Completed
Push — master ( 7d9ab3...0334ca )
by Julito
08:48
created

learnpath   F

Complexity

Total Complexity 1451

Size/Duplication

Total Lines 11895
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 6154
dl 0
loc 11895
rs 0.8
c 5
b 0
f 0
wmc 1451

223 Methods

Rating   Name   Duplication   Size   Complexity  
A getProgressBar() 0 5 1
B update_default_view_mode() 0 33 6
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A togglePublish() 0 27 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
B save_last() 0 52 11
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 308 51
A switch_attempt_mode() 0 16 4
A getAccumulateScormTime() 0 3 1
F create_document() 0 142 26
A get_course_int_id() 0 3 2
D prerequisites_match() 0 69 16
A previous() 0 11 1
A tree_array() 0 4 1
A createCategory() 0 31 1
C get_exercises() 0 132 11
B move_down() 0 54 8
C fixBlockedLinks() 0 64 11
A open() 0 9 1
F edit_item() 0 212 22
A createForum() 0 18 1
A set_jslib() 0 15 2
A update_default_scorm_commit() 0 25 4
B getChildrenToc() 0 49 11
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
C start_current_item() 0 40 16
A displayItemMenu() 0 91 5
A set_publicated_on() 0 22 3
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 31 5
A set_prerequisite() 0 10 1
A set_modified_on() 0 10 1
A get_last() 0 10 2
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 25 2
A get_type() 0 8 3
A get_maker() 0 7 2
A display_link_form() 0 22 2
A getForum() 0 9 1
A get_progress_bar() 0 12 1
A set_preview_image() 0 11 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 86 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
A set_course_int_id() 0 3 1
F createReadOutText() 0 121 27
A get_type_static() 0 16 3
A moveDownCategory() 0 11 2
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A display_item_form() 0 14 1
A select_previous_item_id() 0 22 1
F add_item() 0 214 16
A get_previous_index() 0 16 5
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
B getTOCTree() 0 44 8
F first() 0 71 20
A getItem() 0 7 3
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
B get_links() 0 118 7
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
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
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A set_theme() 0 11 1
F __construct() 0 278 40
A getChapterTypes() 0 4 1
C rl_get_resource_name() 0 92 14
A displayNewSectionForm() 0 17 1
A getSavedFinalItem() 0 13 3
B get_preview_image_path() 0 28 7
B restart() 0 40 6
A set_author() 0 10 1
A cleanItemTitle() 0 5 1
B get_iv_interactions_array() 0 54 8
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A set_attempt_mode() 0 32 5
A close() 0 13 2
A deleteCategory() 0 37 4
A update_display_order() 0 29 5
A getNameNoTags() 0 3 1
B generate_lp_folder() 0 52 7
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 13 1
A moveUpCategory() 0 11 2
C get_mediaplayer() 0 80 13
A set_proximity() 0 15 2
A getProgressFromLpList() 0 32 4
C display_item() 0 81 14
B overview() 0 50 9
A set_expired_on() 0 23 3
F autocomplete_parents() 0 101 17
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
F display_item_prerequisites_form() 0 171 20
B get_scorm_prereq_string() 0 73 11
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
C build_action_menu() 0 165 11
A get_next_item_id() 0 10 3
B get_view() 0 49 7
A getCategoryByCourse() 0 8 1
F is_lp_visible_for_student() 0 141 25
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
A get_complete_items_count() 0 24 5
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 110 2
B delete_item() 0 70 6
B get_forums() 0 112 9
A edit_document() 0 15 4
D move_item() 0 125 18
A getTotalItemsCountWithoutDirs() 0 11 3
A getCurrentAttempt() 0 8 2
A delete_lp_image() 0 17 5
A display_forum_form() 0 16 2
A update_scorm_debug() 0 23 4
A getUseScoreAsProgress() 0 18 5
A toggleCategoryVisibility() 0 19 3
C getParentToc() 0 54 13
F add_lp() 0 142 14
A getFinalItem() 0 12 4
A displayResources() 0 55 2
A setAccumulateScormTime() 0 11 1
A update_reinit() 0 23 4
A getFinalItemTemplate() 0 3 1
D getListArrayToc() 0 67 11
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 171 32
A getCourseCode() 0 3 1
A get_view_id() 0 7 2
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
C toggleCategoryPublish() 0 108 12
A generate_learning_path_folder() 0 23 3
C get_navigation_bar() 0 73 10
A set_hide_toc_frame() 0 15 2
A getCategories() 0 9 1
B get_next_index() 0 23 7
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A toggleVisibility() 0 18 3
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 7 2
A return_new_tree() 0 34 4
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
A get_previous_item_id() 0 5 1
F exportToCourseBuildFormat() 0 199 31
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
A get_level_for_item() 0 11 3
F delete() 0 117 18
A set_maker() 0 14 2
A getEntity() 0 3 1
A get_student_publications() 0 42 3
A get_name() 0 7 2
A lpHasForum() 0 26 1
A display_student_publication_form() 0 15 1
A getStatusCSSClassName() 0 7 2
A setAccumulateWorkTime() 0 14 2
A delete_children_items() 0 22 4
B save_item() 0 47 9
A set_name() 0 37 2
F processBuildMenuElements() 0 441 53

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\ResourceLink;
6
use Chamilo\CoreBundle\Entity\User;
7
use Chamilo\CoreBundle\Framework\Container;
8
use Chamilo\CoreBundle\Repository\CourseRepository;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
12
use Chamilo\CourseBundle\Entity\CDocument;
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\CShortcut;
20
use Chamilo\CourseBundle\Entity\CStudentPublication;
21
use Chamilo\CourseBundle\Entity\CTool;
22
use ChamiloSession as Session;
23
use Gedmo\Sortable\Entity\Repository\SortableRepository;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
28
/**
29
 * Class learnpath
30
 * This class defines the parent attributes and methods for Chamilo learnpaths
31
 * and SCORM learnpaths. It is used by the scorm class.
32
 *
33
 * @todo decouple class
34
 *
35
 * @author  Yannick Warnier <[email protected]>
36
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
37
 */
38
class learnpath
39
{
40
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
41
    public const STATUS_CSS_CLASS_NAME = [
42
        'not attempted' => 'scorm_not_attempted',
43
        'incomplete' => 'scorm_not_attempted',
44
        'failed' => 'scorm_failed',
45
        'completed' => 'scorm_completed',
46
        'passed' => 'scorm_completed',
47
        'succeeded' => 'scorm_completed',
48
        'browsed' => 'scorm_completed',
49
    ];
50
51
    public $attempt = 0; // The number for the current ID view.
52
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
53
    public $current; // Id of the current item the user is viewing.
54
    public $current_score; // The score of the current item.
55
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
56
    public $current_time_stop; // The time the user closed this resource.
57
    public $default_status = 'not attempted';
58
    public $encoding = 'UTF-8';
59
    public $error = '';
60
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
61
    public $index; // The index of the active learnpath_item in $ordered_items array.
62
    /** @var learnpathItem[] */
63
    public $items = [];
64
    public $last; // item_id of last item viewed in the learning path.
65
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
66
    public $license; // Which license this course has been given - not used yet on 20060522.
67
    public $lp_id; // DB iid for this learnpath.
68
    public $lp_view_id; // DB ID for lp_view
69
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
70
    public $message = '';
71
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
72
    public $name; // Learnpath name (they generally have one).
73
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
74
    public $path = ''; // Path inside the scorm directory (if scorm).
75
    public $theme; // The current theme of the learning path.
76
    public $preview_image; // The current image of the learning path.
77
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
78
    public $accumulateWorkTime; // The min time of learnpath
79
80
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
81
    public $prevent_reinit = 1;
82
83
    // Describes the mode of progress bar display.
84
    public $seriousgame_mode = 0;
85
    public $progress_bar_mode = '%';
86
87
    // Percentage progress as saved in the db.
88
    public $progress_db = 0;
89
    public $proximity; // Wether the content is distant or local or unknown.
90
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
91
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
92
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
93
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
94
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
95
    public $user_id; //ID of the user that is viewing/using the course
96
    public $update_queue = [];
97
    public $scorm_debug = 0;
98
    public $arrMenu = []; // Array for the menu items.
99
    public $debug = 0; // Logging level.
100
    public $lp_session_id = 0;
101
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
102
    public $prerequisite = 0;
103
    public $use_max_score = 1; // 1 or 0
104
    public $subscribeUsers = 0; // Subscribe users or not
105
    public $created_on = '';
106
    public $modified_on = '';
107
    public $publicated_on = '';
108
    public $expired_on = '';
109
    public $ref = null;
110
    public $course_int_id;
111
    public $course_info = [];
112
    public $categoryId;
113
    public $entity;
114
115
    /**
116
     * Constructor.
117
     * Needs a database handler, a course code and a learnpath id from the database.
118
     * Also builds the list of items into $this->items.
119
     *
120
     * @param string $course  Course code
121
     * @param int    $lp_id   c_lp.iid
122
     * @param int    $user_id
123
     */
124
    public function __construct($course, $lp_id, $user_id)
125
    {
126
        $debug = $this->debug;
127
        $this->encoding = api_get_system_encoding();
128
        if (empty($course)) {
129
            $course = api_get_course_id();
130
        }
131
        $course_info = api_get_course_info($course);
132
        if (!empty($course_info)) {
133
            $this->cc = $course_info['code'];
134
            $this->course_info = $course_info;
135
            $course_id = $course_info['real_id'];
136
        } else {
137
            $this->error = 'Course code does not exist in database.';
138
        }
139
140
        $lp_id = (int) $lp_id;
141
        $course_id = (int) $course_id;
142
        $this->set_course_int_id($course_id);
143
        // Check learnpath ID.
144
        if (empty($lp_id) || empty($course_id)) {
145
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
146
        } else {
147
            $repo = Container::getLpRepository();
148
            /** @var CLp $entity */
149
            $entity = $repo->find($lp_id);
150
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
151
                $this->entity = $entity;
152
                $this->lp_id = $lp_id;
153
                $this->type = $entity->getLpType();
154
                $this->name = stripslashes($entity->getName());
155
                $this->proximity = $entity->getContentLocal();
156
                $this->theme = $entity->getTheme();
157
                $this->maker = $entity->getContentLocal();
158
                $this->prevent_reinit = $entity->getPreventReinit();
159
                $this->seriousgame_mode = $entity->getSeriousgameMode();
160
                $this->license = $entity->getContentLicense();
161
                $this->scorm_debug = $entity->getDebug();
162
                $this->js_lib = $entity->getJsLib();
163
                $this->path = $entity->getPath();
164
                $this->preview_image = $entity->getPreviewImage();
165
                $this->author = $entity->getAuthor();
166
                $this->hide_toc_frame = $entity->getHideTocFrame();
167
                $this->lp_session_id = $entity->getSessionId();
168
                $this->use_max_score = $entity->getUseMaxScore();
169
                $this->subscribeUsers = $entity->getSubscribeUsers();
170
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
171
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
172
                $this->ref = $entity->getRef();
173
                $this->categoryId = $entity->getCategoryId();
174
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
175
176
                if (!empty($entity->getPublicatedOn())) {
177
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
178
                }
179
180
                if (!empty($entity->getExpiredOn())) {
181
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
182
                }
183
                if (2 == $this->type) {
184
                    if (1 == $entity->getForceCommit()) {
185
                        $this->force_commit = true;
186
                    }
187
                }
188
                $this->mode = $entity->getDefaultViewMod();
189
190
                // Check user ID.
191
                if (empty($user_id)) {
192
                    $this->error = 'User ID is empty';
193
                } else {
194
                    $userInfo = api_get_user_info($user_id);
195
                    if (!empty($userInfo)) {
196
                        $this->user_id = $userInfo['user_id'];
197
                    } else {
198
                        $this->error = 'User ID does not exist in database #'.$user_id;
199
                    }
200
                }
201
202
                // End of variables checking.
203
                $session_id = api_get_session_id();
204
                //  Get the session condition for learning paths of the base + session.
205
                $session = api_get_session_condition($session_id);
206
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
207
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
208
209
                // Selecting by view_count descending allows to get the highest view_count first.
210
                $sql = "SELECT * FROM $lp_table
211
                        WHERE
212
                            c_id = $course_id AND
213
                            lp_id = $lp_id AND
214
                            user_id = $user_id
215
                            $session
216
                        ORDER BY view_count DESC";
217
                $res = Database::query($sql);
218
219
                if (Database::num_rows($res) > 0) {
220
                    $row = Database::fetch_array($res);
221
                    $this->attempt = $row['view_count'];
222
                    $this->lp_view_id = $row['id'];
223
                    $this->last_item_seen = $row['last_item'];
224
                    $this->progress_db = $row['progress'];
225
                    $this->lp_view_session_id = $row['session_id'];
226
                } elseif (!api_is_invitee()) {
227
                    $this->attempt = 1;
228
                    $params = [
229
                        'c_id' => $course_id,
230
                        'lp_id' => $lp_id,
231
                        'user_id' => $user_id,
232
                        'view_count' => 1,
233
                        'session_id' => $session_id,
234
                        'last_item' => 0,
235
                    ];
236
                    $this->last_item_seen = 0;
237
                    $this->lp_view_session_id = $session_id;
238
                    $this->lp_view_id = Database::insert($lp_table, $params);
239
                    if (!empty($this->lp_view_id)) {
240
                        $sql = "UPDATE $lp_table SET id = iid
241
                                WHERE iid = ".$this->lp_view_id;
242
                        Database::query($sql);
243
                    }
244
                }
245
246
                // Initialise items.
247
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
248
                $sql = "SELECT * FROM $lp_item_table
249
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
250
                        ORDER BY parent_item_id, display_order";
251
                $res = Database::query($sql);
252
253
                $lp_item_id_list = [];
254
                while ($row = Database::fetch_array($res)) {
255
                    $lp_item_id_list[] = $row['iid'];
256
                    switch ($this->type) {
257
                        case 3: //aicc
258
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
259
                            if (is_object($oItem)) {
260
                                $my_item_id = $oItem->get_id();
261
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
262
                                $oItem->set_prevent_reinit($this->prevent_reinit);
263
                                // Don't use reference here as the next loop will make the pointed object change.
264
                                $this->items[$my_item_id] = $oItem;
265
                                $this->refs_list[$oItem->ref] = $my_item_id;
266
                            }
267
                            break;
268
                        case 2:
269
                            $oItem = new scormItem('db', $row['iid'], $course_id);
270
                            if (is_object($oItem)) {
271
                                $my_item_id = $oItem->get_id();
272
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
273
                                $oItem->set_prevent_reinit($this->prevent_reinit);
274
                                // Don't use reference here as the next loop will make the pointed object change.
275
                                $this->items[$my_item_id] = $oItem;
276
                                $this->refs_list[$oItem->ref] = $my_item_id;
277
                            }
278
                            break;
279
                        case 1:
280
                        default:
281
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
282
                            if (is_object($oItem)) {
283
                                $my_item_id = $oItem->get_id();
284
                                // Moved down to when we are sure the item_view exists.
285
                                //$oItem->set_lp_view($this->lp_view_id);
286
                                $oItem->set_prevent_reinit($this->prevent_reinit);
287
                                // Don't use reference here as the next loop will make the pointed object change.
288
                                $this->items[$my_item_id] = $oItem;
289
                                $this->refs_list[$my_item_id] = $my_item_id;
290
                            }
291
                            break;
292
                    }
293
294
                    // Setting the object level with variable $this->items[$i][parent]
295
                    foreach ($this->items as $itemLPObject) {
296
                        $level = self::get_level_for_item(
297
                            $this->items,
298
                            $itemLPObject->db_id
299
                        );
300
                        $itemLPObject->level = $level;
301
                    }
302
303
                    // Setting the view in the item object.
304
                    if (is_object($this->items[$row['iid']])) {
305
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
306
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
307
                            $this->items[$row['iid']]->current_start_time = 0;
308
                            $this->items[$row['iid']]->current_stop_time = 0;
309
                        }
310
                    }
311
                }
312
313
                if (!empty($lp_item_id_list)) {
314
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
315
                    if (!empty($lp_item_id_list_to_string)) {
316
                        // Get last viewing vars.
317
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
318
                        // This query should only return one or zero result.
319
                        $sql = "SELECT lp_item_id, status
320
                                FROM $itemViewTable
321
                                WHERE
322
                                    c_id = $course_id AND
323
                                    lp_view_id = ".$this->get_view_id()." AND
324
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
325
                                ORDER BY view_count DESC ";
326
                        $status_list = [];
327
                        $res = Database::query($sql);
328
                        while ($row = Database:: fetch_array($res)) {
329
                            $status_list[$row['lp_item_id']] = $row['status'];
330
                        }
331
332
                        foreach ($lp_item_id_list as $item_id) {
333
                            if (isset($status_list[$item_id])) {
334
                                $status = $status_list[$item_id];
335
                                if (is_object($this->items[$item_id])) {
336
                                    $this->items[$item_id]->set_status($status);
337
                                    if (empty($status)) {
338
                                        $this->items[$item_id]->set_status(
339
                                            $this->default_status
340
                                        );
341
                                    }
342
                                }
343
                            } else {
344
                                if (!api_is_invitee()) {
345
                                    if (is_object($this->items[$item_id])) {
346
                                        $this->items[$item_id]->set_status(
347
                                            $this->default_status
348
                                        );
349
                                    }
350
351
                                    if (!empty($this->lp_view_id)) {
352
                                        // Add that row to the lp_item_view table so that
353
                                        // we have something to show in the stats page.
354
                                        $params = [
355
                                            'c_id' => $course_id,
356
                                            'lp_item_id' => $item_id,
357
                                            'lp_view_id' => $this->lp_view_id,
358
                                            'view_count' => 1,
359
                                            'status' => 'not attempted',
360
                                            'start_time' => time(),
361
                                            'total_time' => 0,
362
                                            'score' => 0,
363
                                        ];
364
                                        $insertId = Database::insert($itemViewTable, $params);
365
366
                                        if ($insertId) {
367
                                            $sql = "UPDATE $itemViewTable SET id = iid
368
                                                    WHERE iid = $insertId";
369
                                            Database::query($sql);
370
                                        }
371
372
                                        $this->items[$item_id]->set_lp_view(
373
                                            $this->lp_view_id,
374
                                            $course_id
375
                                        );
376
                                    }
377
                                }
378
                            }
379
                        }
380
                    }
381
                }
382
383
                $this->ordered_items = self::get_flat_ordered_items_list(
384
                    $this->get_id(),
385
                    0,
386
                    $course_id
387
                );
388
                $this->max_ordered_items = 0;
389
                foreach ($this->ordered_items as $index => $dummy) {
390
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
391
                        $this->max_ordered_items = $index;
392
                    }
393
                }
394
                // TODO: Define the current item better.
395
                $this->first();
396
                if ($debug) {
397
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
398
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
399
                }
400
            } else {
401
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
402
            }
403
        }
404
    }
405
406
    public function getEntity(): CLp
407
    {
408
        return $this->entity;
409
    }
410
411
    /**
412
     * @return string
413
     */
414
    public function getCourseCode()
415
    {
416
        return $this->cc;
417
    }
418
419
    /**
420
     * @return int
421
     */
422
    public function get_course_int_id()
423
    {
424
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
425
    }
426
427
    /**
428
     * @param $course_id
429
     *
430
     * @return int
431
     */
432
    public function set_course_int_id($course_id)
433
    {
434
        return $this->course_int_id = (int) $course_id;
435
    }
436
437
    /**
438
     * Function rewritten based on old_add_item() from Yannick Warnier.
439
     * Due the fact that users can decide where the item should come, I had to overlook this function and
440
     * I found it better to rewrite it. Old function is still available.
441
     * Added also the possibility to add a description.
442
     *
443
     * @param int    $parent
444
     * @param int    $previous
445
     * @param string $type
446
     * @param int    $id               resource ID (ref)
447
     * @param string $title
448
     * @param string $description
449
     * @param int    $prerequisites
450
     * @param int    $max_time_allowed
451
     * @param int    $userId
452
     *
453
     * @return int
454
     */
455
    public function add_item(
456
        $parent,
457
        $previous,
458
        $type = 'dir',
459
        $id,
460
        $title,
461
        $description,
462
        $prerequisites = 0,
463
        $max_time_allowed = 0,
464
        $userId = 0
465
    ) {
466
        $course_id = $this->course_info['real_id'];
467
        if (empty($course_id)) {
468
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
469
            $this->course_info = api_get_course_info($this->cc);
470
            $course_id = $this->course_info['real_id'];
471
        }
472
        $userId = empty($userId) ? api_get_user_id() : $userId;
473
        $sessionId = api_get_session_id();
474
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
475
        $_course = $this->course_info;
476
        $parent = (int) $parent;
477
        $previous = (int) $previous;
478
        $id = (int) $id;
479
        $max_time_allowed = htmlentities($max_time_allowed);
480
        if (empty($max_time_allowed)) {
481
            $max_time_allowed = 0;
482
        }
483
        $sql = "SELECT COUNT(iid) AS num
484
                FROM $tbl_lp_item
485
                WHERE
486
                    c_id = $course_id AND
487
                    lp_id = ".$this->get_id()." AND
488
                    parent_item_id = $parent ";
489
490
        $res_count = Database::query($sql);
491
        $row = Database::fetch_array($res_count);
492
        $num = $row['num'];
493
494
        $tmp_previous = 0;
495
        $display_order = 0;
496
        $next = 0;
497
        if ($num > 0) {
498
            if (empty($previous)) {
499
                $sql = "SELECT iid, next_item_id, display_order
500
                        FROM $tbl_lp_item
501
                        WHERE
502
                            c_id = $course_id AND
503
                            lp_id = ".$this->get_id()." AND
504
                            parent_item_id = $parent AND
505
                            previous_item_id = 0 OR
506
                            previous_item_id = $parent";
507
                $result = Database::query($sql);
508
                $row = Database::fetch_array($result);
509
                if ($row) {
510
                    $next = $row['iid'];
511
                }
512
            } else {
513
                $previous = (int) $previous;
514
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
515
						FROM $tbl_lp_item
516
                        WHERE
517
                            c_id = $course_id AND
518
                            lp_id = ".$this->get_id()." AND
519
                            id = $previous";
520
                $result = Database::query($sql);
521
                $row = Database::fetch_array($result);
522
                if ($row) {
523
                    $tmp_previous = $row['iid'];
524
                    $next = $row['next_item_id'];
525
                    $display_order = $row['display_order'];
526
                }
527
            }
528
        }
529
530
        $id = (int) $id;
531
        $typeCleaned = Database::escape_string($type);
532
        $max_score = 100;
533
        if ($type === 'quiz' && $id) {
534
            $sql = 'SELECT SUM(ponderation)
535
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
536
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
537
                    ON
538
                        quiz_question.id = quiz_rel_question.question_id AND
539
                        quiz_question.c_id = quiz_rel_question.c_id
540
                    WHERE
541
                        quiz_rel_question.exercice_id = '.$id." AND
542
                        quiz_question.c_id = $course_id AND
543
                        quiz_rel_question.c_id = $course_id ";
544
            $rsQuiz = Database::query($sql);
545
            $max_score = Database::result($rsQuiz, 0, 0);
546
547
            // Disabling the exercise if we add it inside a LP
548
            $exercise = new Exercise($course_id);
549
            $exercise->read($id);
550
            $exercise->disable();
551
            $exercise->save();
552
        }
553
554
        $params = [
555
            'c_id' => $course_id,
556
            'lp_id' => $this->get_id(),
557
            'item_type' => $typeCleaned,
558
            'ref' => '',
559
            'title' => $title,
560
            'description' => $description,
561
            'path' => $id,
562
            'max_score' => $max_score,
563
            'parent_item_id' => $parent,
564
            'previous_item_id' => $previous,
565
            'next_item_id' => (int) $next,
566
            'display_order' => $display_order + 1,
567
            'prerequisite' => $prerequisites,
568
            'max_time_allowed' => $max_time_allowed,
569
            'min_score' => 0,
570
            'launch_data' => '',
571
        ];
572
573
        if (0 != $prerequisites) {
574
            $params['prerequisite'] = $prerequisites;
575
        }
576
577
        $new_item_id = Database::insert($tbl_lp_item, $params);
578
        if ($new_item_id) {
579
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
580
            Database::query($sql);
581
582
            if (!empty($next)) {
583
                $sql = "UPDATE $tbl_lp_item
584
                        SET previous_item_id = $new_item_id
585
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
586
                Database::query($sql);
587
            }
588
589
            // Update the item that should be before the new item.
590
            if (!empty($tmp_previous)) {
591
                $sql = "UPDATE $tbl_lp_item
592
                        SET next_item_id = $new_item_id
593
                        WHERE c_id = $course_id AND id = $tmp_previous";
594
                Database::query($sql);
595
            }
596
597
            // Update all the items after the new item.
598
            $sql = "UPDATE $tbl_lp_item
599
                        SET display_order = display_order + 1
600
                    WHERE
601
                        c_id = $course_id AND
602
                        lp_id = ".$this->get_id()." AND
603
                        iid <> $new_item_id AND
604
                        parent_item_id = $parent AND
605
                        display_order > $display_order";
606
            Database::query($sql);
607
608
            // Update the item that should come after the new item.
609
            $sql = "UPDATE $tbl_lp_item
610
                    SET ref = $new_item_id
611
                    WHERE c_id = $course_id AND iid = $new_item_id";
612
            Database::query($sql);
613
614
            $sql = "UPDATE $tbl_lp_item
615
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
616
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
617
            Database::query($sql);
618
619
            // Upload audio.
620
            if (!empty($_FILES['mp3']['name'])) {
621
                // Create the audio folder if it does not exist yet.
622
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
623
                if (!is_dir($filepath.'audio')) {
624
                    mkdir(
625
                        $filepath.'audio',
626
                        api_get_permissions_for_new_directories()
627
                    );
628
                    $audio_id = DocumentManager::addDocument(
629
                        $_course,
630
                        '/audio',
631
                        'folder',
632
                        0,
633
                        'audio',
634
                        '',
635
                        0,
636
                        true,
637
                        null,
638
                        $sessionId,
639
                        $userId
640
                    );
641
                }
642
643
                $file_path = handle_uploaded_document(
644
                    $_course,
645
                    $_FILES['mp3'],
646
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
647
                    '/audio',
648
                    $userId,
649
                    '',
650
                    '',
651
                    '',
652
                    '',
653
                    false
654
                );
655
656
                // Getting the filename only.
657
                $file_components = explode('/', $file_path);
658
                $file = $file_components[count($file_components) - 1];
659
660
                // Store the mp3 file in the lp_item table.
661
                $sql = "UPDATE $tbl_lp_item SET
662
                          audio = '".Database::escape_string($file)."'
663
                        WHERE iid = '".intval($new_item_id)."'";
664
                Database::query($sql);
665
            }
666
        }
667
668
        return $new_item_id;
669
    }
670
671
    /**
672
     * Static admin function allowing addition of a learnpath to a course.
673
     *
674
     * @param string $courseCode
675
     * @param string $name
676
     * @param string $description
677
     * @param string $learnpath
678
     * @param string $origin
679
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
680
     * @param string $publicated_on
681
     * @param string $expired_on
682
     * @param int    $categoryId
683
     * @param int    $userId
684
     *
685
     * @return int The new learnpath ID on success, 0 on failure
686
     */
687
    public static function add_lp(
688
        $courseCode,
689
        $name,
690
        $description = '',
691
        $learnpath = 'guess',
692
        $origin = 'zip',
693
        $zipname = '',
694
        $publicated_on = '',
695
        $expired_on = '',
696
        $categoryId = 0,
697
        $userId = 0
698
    ) {
699
        global $charset;
700
701
        if (!empty($courseCode)) {
702
            $courseInfo = api_get_course_info($courseCode);
703
            $course_id = $courseInfo['real_id'];
704
        } else {
705
            $course_id = api_get_course_int_id();
706
            $courseInfo = api_get_course_info();
707
        }
708
709
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
710
        // Check course code exists.
711
        // Check lp_name doesn't exist, otherwise append something.
712
        $i = 0;
713
        $categoryId = (int) $categoryId;
714
        // Session id.
715
        $session_id = api_get_session_id();
716
        $userId = empty($userId) ? api_get_user_id() : $userId;
717
718
        if (empty($publicated_on)) {
719
            $publicated_on = null;
720
        } else {
721
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
722
        }
723
724
        if (empty($expired_on)) {
725
            $expired_on = null;
726
        } else {
727
            $expired_on = api_get_utc_datetime($expired_on, true, true);
728
        }
729
730
        $check_name = "SELECT * FROM $tbl_lp
731
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
732
        $res_name = Database::query($check_name);
733
734
        while (Database::num_rows($res_name)) {
735
            // There is already one such name, update the current one a bit.
736
            $i++;
737
            $name = $name.' - '.$i;
738
            $check_name = "SELECT * FROM $tbl_lp
739
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
740
            $res_name = Database::query($check_name);
741
        }
742
        // New name does not exist yet; keep it.
743
        // Escape description.
744
        // Kevin: added htmlentities().
745
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
746
        $type = 1;
747
        switch ($learnpath) {
748
            case 'guess':
749
            case 'aicc':
750
                break;
751
            case 'dokeos':
752
            case 'chamilo':
753
                $type = 1;
754
                break;
755
        }
756
757
        $id = null;
758
        switch ($origin) {
759
            case 'zip':
760
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
761
                break;
762
            case 'manual':
763
            default:
764
                $get_max = "SELECT MAX(display_order)
765
                            FROM $tbl_lp WHERE c_id = $course_id";
766
                $res_max = Database::query($get_max);
767
                if (Database::num_rows($res_max) < 1) {
768
                    $dsp = 1;
769
                } else {
770
                    $row = Database::fetch_array($res_max);
771
                    $dsp = $row[0] + 1;
772
                }
773
774
                $lp = new CLp();
775
                $lp
776
                    ->setCId($course_id)
777
                    ->setLpType($type)
778
                    ->setName($name)
779
                    ->setDescription($description)
780
                    ->setDisplayOrder($dsp)
781
                    ->setSessionId($session_id)
782
                    ->setCategoryId($categoryId)
783
                    ->setPublicatedOn($publicated_on)
784
                    ->setExpiredOn($expired_on)
785
                ;
786
787
                $repo = Container::getLpRepository();
788
                $em = $repo->getEntityManager();
789
                $em->persist($lp);
790
                $courseEntity = api_get_course_entity($courseInfo['real_id']);
791
792
                $repo->addResourceToCourse(
793
                    $lp,
794
                    ResourceLink::VISIBILITY_PUBLISHED,
795
                    api_get_user_entity(api_get_user_id()),
796
                    $courseEntity,
797
                    api_get_session_entity(),
798
                    api_get_group_entity()
799
                );
800
801
                $em->flush();
802
                if ($lp->getIid()) {
803
                    $id = $lp->getIid();
804
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
805
                    Database::query($sql);
806
                }
807
808
                // Insert into item_property.
809
                /*api_item_property_update(
810
                    $courseInfo,
811
                    TOOL_LEARNPATH,
812
                    $id,
813
                    'LearnpathAdded',
814
                    $userId
815
                );
816
                api_set_default_visibility(
817
                    $id,
818
                    TOOL_LEARNPATH,
819
                    0,
820
                    $courseInfo,
821
                    $session_id,
822
                    $userId
823
                );*/
824
825
                break;
826
        }
827
828
        return $id;
829
    }
830
831
    /**
832
     * Auto completes the parents of an item in case it's been completed or passed.
833
     *
834
     * @param int $item Optional ID of the item from which to look for parents
835
     */
836
    public function autocomplete_parents($item)
837
    {
838
        $debug = $this->debug;
839
840
        if (empty($item)) {
841
            $item = $this->current;
842
        }
843
844
        $currentItem = $this->getItem($item);
845
        if ($currentItem) {
846
            $parent_id = $currentItem->get_parent();
847
            $parent = $this->getItem($parent_id);
848
            if ($parent) {
849
                // if $item points to an object and there is a parent.
850
                if ($debug) {
851
                    error_log(
852
                        'Autocompleting parent of item '.$item.' '.
853
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
854
                        0
855
                    );
856
                }
857
858
                // New experiment including failed and browsed in completed status.
859
                //$current_status = $currentItem->get_status();
860
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
861
                // Fixes chapter auto complete
862
                if (true) {
863
                    // If the current item is completed or passes or succeeded.
864
                    $updateParentStatus = true;
865
                    if ($debug) {
866
                        error_log('Status of current item is alright');
867
                    }
868
869
                    foreach ($parent->get_children() as $childItemId) {
870
                        $childItem = $this->getItem($childItemId);
871
872
                        // If children was not set try to get the info
873
                        if (empty($childItem->db_item_view_id)) {
874
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
875
                        }
876
877
                        // Check all his brothers (parent's children) for completion status.
878
                        if ($childItemId != $item) {
879
                            if ($debug) {
880
                                error_log(
881
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
882
                                    0
883
                                );
884
                            }
885
                            // Trying completing parents of failed and browsed items as well.
886
                            if ($childItem->status_is(
887
                                [
888
                                    'completed',
889
                                    'passed',
890
                                    'succeeded',
891
                                    'browsed',
892
                                    'failed',
893
                                ]
894
                            )
895
                            ) {
896
                                // Keep completion status to true.
897
                                continue;
898
                            } else {
899
                                if ($debug > 2) {
900
                                    error_log(
901
                                        '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,
902
                                        0
903
                                    );
904
                                }
905
                                $updateParentStatus = false;
906
                                break;
907
                            }
908
                        }
909
                    }
910
911
                    if ($updateParentStatus) {
912
                        // If all the children were completed:
913
                        $parent->set_status('completed');
914
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
915
                        // Force the status to "completed"
916
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
917
                        $this->update_queue[$parent->get_id()] = 'completed';
918
                        if ($debug) {
919
                            error_log(
920
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
921
                                print_r($this->update_queue, 1),
922
                                0
923
                            );
924
                        }
925
                        // Recursive call.
926
                        $this->autocomplete_parents($parent->get_id());
927
                    }
928
                }
929
            } else {
930
                if ($debug) {
931
                    error_log("Parent #$parent_id does not exists");
932
                }
933
            }
934
        } else {
935
            if ($debug) {
936
                error_log("#$item is an item that doesn't have parents");
937
            }
938
        }
939
    }
940
941
    /**
942
     * Closes the current resource.
943
     *
944
     * Stops the timer
945
     * Saves into the database if required
946
     * Clears the current resource data from this object
947
     *
948
     * @return bool True on success, false on failure
949
     */
950
    public function close()
951
    {
952
        if (empty($this->lp_id)) {
953
            $this->error = 'Trying to close this learnpath but no ID is set';
954
955
            return false;
956
        }
957
        $this->current_time_stop = time();
958
        $this->ordered_items = [];
959
        $this->index = 0;
960
        unset($this->lp_id);
961
        //unset other stuff
962
        return true;
963
    }
964
965
    /**
966
     * Static admin function allowing removal of a learnpath.
967
     *
968
     * @param array  $courseInfo
969
     * @param int    $id         Learnpath ID
970
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
971
     *
972
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
973
     */
974
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
975
    {
976
        $course_id = api_get_course_int_id();
977
        if (!empty($courseInfo)) {
978
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
979
        }
980
981
        // TODO: Implement a way of getting this to work when the current object is not set.
982
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
983
        // If an ID is specifically given and the current LP is not the same, prevent delete.
984
        if (!empty($id) && ($id != $this->lp_id)) {
985
            return false;
986
        }
987
988
        $lp = Database::get_course_table(TABLE_LP_MAIN);
989
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
990
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
991
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
992
993
        // Delete lp item id.
994
        foreach ($this->items as $lpItemId => $dummy) {
995
            $sql = "DELETE FROM $lp_item_view
996
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
997
            Database::query($sql);
998
        }
999
1000
        // Proposed by Christophe (nickname: clefevre)
1001
        $sql = "DELETE FROM $lp_item
1002
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1003
        Database::query($sql);
1004
1005
        $sql = "DELETE FROM $lp_view
1006
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1007
        Database::query($sql);
1008
1009
        self::toggle_publish($this->lp_id, 'i');
1010
        //self::toggle_publish($this->lp_id, 'i');
1011
1012
        if (2 == $this->type || 3 == $this->type) {
1013
            // This is a scorm learning path, delete the files as well.
1014
            $sql = "SELECT path FROM $lp
1015
                    WHERE iid = ".$this->lp_id;
1016
            $res = Database::query($sql);
1017
            if (Database::num_rows($res) > 0) {
1018
                $row = Database::fetch_array($res);
1019
                $path = $row['path'];
1020
                $sql = "SELECT id FROM $lp
1021
                        WHERE
1022
                            c_id = $course_id AND
1023
                            path = '$path' AND
1024
                            iid != ".$this->lp_id;
1025
                $res = Database::query($sql);
1026
                if (Database::num_rows($res) > 0) {
1027
                    // Another learning path uses this directory, so don't delete it.
1028
                    if ($this->debug > 2) {
1029
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1030
                    }
1031
                } else {
1032
                    // No other LP uses that directory, delete it.
1033
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1034
                    // The absolute system path for this course.
1035
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1036
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1037
                        if ($this->debug > 2) {
1038
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1039
                        }
1040
                        // Proposed by Christophe (clefevre).
1041
                        if (0 == strcmp(substr($path, -2), "/.")) {
1042
                            $path = substr($path, 0, -1); // Remove "." at the end.
1043
                        }
1044
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1045
                        rmdirr($course_scorm_dir.$path);
1046
                    }
1047
                }
1048
            }
1049
        }
1050
1051
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1052
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1053
        // Delete tools
1054
        $sql = "DELETE FROM $tbl_tool
1055
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1056
        Database::query($sql);*/
1057
1058
        /*$sql = "DELETE FROM $lp
1059
                WHERE iid = ".$this->lp_id;
1060
        Database::query($sql);*/
1061
        $repo = Container::getLpRepository();
1062
        $lp = $repo->find($this->lp_id);
1063
        $repo->getEntityManager()->remove($lp);
1064
        $repo->getEntityManager()->flush();
1065
1066
        // Updates the display order of all lps.
1067
        $this->update_display_order();
1068
1069
        /*api_item_property_update(
1070
            api_get_course_info(),
1071
            TOOL_LEARNPATH,
1072
            $this->lp_id,
1073
            'delete',
1074
            api_get_user_id()
1075
        );*/
1076
1077
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1078
            api_get_course_id(),
1079
            4,
1080
            $id,
1081
            api_get_session_id()
1082
        );
1083
1084
        if (false !== $link_info) {
1085
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1086
        }
1087
1088
        if ('true' == api_get_setting('search_enabled')) {
1089
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1090
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1091
        }
1092
    }
1093
1094
    /**
1095
     * Removes all the children of one item - dangerous!
1096
     *
1097
     * @param int $id Element ID of which children have to be removed
1098
     *
1099
     * @return int Total number of children removed
1100
     */
1101
    public function delete_children_items($id)
1102
    {
1103
        $course_id = $this->course_info['real_id'];
1104
1105
        $num = 0;
1106
        $id = (int) $id;
1107
        if (empty($id) || empty($course_id)) {
1108
            return false;
1109
        }
1110
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1111
        $sql = "SELECT * FROM $lp_item
1112
                WHERE c_id = $course_id AND parent_item_id = $id";
1113
        $res = Database::query($sql);
1114
        while ($row = Database::fetch_array($res)) {
1115
            $num += $this->delete_children_items($row['iid']);
1116
            $sql = "DELETE FROM $lp_item
1117
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1118
            Database::query($sql);
1119
            $num++;
1120
        }
1121
1122
        return $num;
1123
    }
1124
1125
    /**
1126
     * Removes an item from the current learnpath.
1127
     *
1128
     * @param int $id Elem ID (0 if first)
1129
     *
1130
     * @return int Number of elements moved
1131
     *
1132
     * @todo implement resource removal
1133
     */
1134
    public function delete_item($id)
1135
    {
1136
        $course_id = api_get_course_int_id();
1137
        $id = (int) $id;
1138
        // TODO: Implement the resource removal.
1139
        if (empty($id) || empty($course_id)) {
1140
            return false;
1141
        }
1142
        // First select item to get previous, next, and display order.
1143
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1144
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1145
        $res_sel = Database::query($sql_sel);
1146
        if (Database::num_rows($res_sel) < 1) {
1147
            return false;
1148
        }
1149
        $row = Database::fetch_array($res_sel);
1150
        $previous = $row['previous_item_id'];
1151
        $next = $row['next_item_id'];
1152
        $display = $row['display_order'];
1153
        $parent = $row['parent_item_id'];
1154
        $lp = $row['lp_id'];
1155
        // Delete children items.
1156
        $this->delete_children_items($id);
1157
        // Now delete the item.
1158
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1159
        Database::query($sql_del);
1160
        // Now update surrounding items.
1161
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1162
                    WHERE iid = $previous";
1163
        Database::query($sql_upd);
1164
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1165
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1166
        Database::query($sql_upd);
1167
        // Now update all following items with new display order.
1168
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1169
                    WHERE
1170
                        c_id = $course_id AND
1171
                        lp_id = $lp AND
1172
                        parent_item_id = $parent AND
1173
                        display_order > $display";
1174
        Database::query($sql_all);
1175
1176
        //Removing prerequisites since the item will not longer exist
1177
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1178
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1179
        Database::query($sql_all);
1180
1181
        $sql = "UPDATE $lp_item
1182
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1183
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1184
        Database::query($sql);
1185
1186
        // Remove from search engine if enabled.
1187
        if ('true' === api_get_setting('search_enabled')) {
1188
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1189
            $sql = 'SELECT * FROM %s
1190
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1191
                    LIMIT 1';
1192
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1193
            $res = Database::query($sql);
1194
            if (Database::num_rows($res) > 0) {
1195
                $row2 = Database::fetch_array($res);
1196
                $di = new ChamiloIndexer();
1197
                $di->remove_document($row2['search_did']);
1198
            }
1199
            $sql = 'DELETE FROM %s
1200
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1201
                    LIMIT 1';
1202
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1203
            Database::query($sql);
1204
        }
1205
    }
1206
1207
    /**
1208
     * Updates an item's content in place.
1209
     *
1210
     * @param int    $id               Element ID
1211
     * @param int    $parent           Parent item ID
1212
     * @param int    $previous         Previous item ID
1213
     * @param string $title            Item title
1214
     * @param string $description      Item description
1215
     * @param string $prerequisites    Prerequisites (optional)
1216
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1217
     * @param int    $max_time_allowed
1218
     * @param string $url
1219
     *
1220
     * @return bool True on success, false on error
1221
     */
1222
    public function edit_item(
1223
        $id,
1224
        $parent,
1225
        $previous,
1226
        $title,
1227
        $description,
1228
        $prerequisites = '0',
1229
        $audio = [],
1230
        $max_time_allowed = 0,
1231
        $url = ''
1232
    ) {
1233
        $course_id = api_get_course_int_id();
1234
        $_course = api_get_course_info();
1235
        $id = (int) $id;
1236
1237
        if (empty($max_time_allowed)) {
1238
            $max_time_allowed = 0;
1239
        }
1240
1241
        if (empty($id) || empty($_course)) {
1242
            return false;
1243
        }
1244
1245
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1246
        $sql = "SELECT * FROM $tbl_lp_item
1247
                WHERE iid = $id";
1248
        $res_select = Database::query($sql);
1249
        $row_select = Database::fetch_array($res_select);
1250
        $audio_update_sql = '';
1251
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1252
            // Create the audio folder if it does not exist yet.
1253
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1254
            if (!is_dir($filepath.'audio')) {
1255
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1256
                $audio_id = DocumentManager::addDocument(
1257
                    $_course,
1258
                    '/audio',
1259
                    'folder',
1260
                    0,
1261
                    'audio'
1262
                );
1263
            }
1264
1265
            // Upload file in documents.
1266
            $pi = pathinfo($audio['name']);
1267
            if ('mp3' === $pi['extension']) {
1268
                $c_det = api_get_course_info($this->cc);
1269
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1270
                $path = handle_uploaded_document(
1271
                    $c_det,
1272
                    $audio,
1273
                    $bp,
1274
                    '/audio',
1275
                    api_get_user_id(),
1276
                    0,
1277
                    null,
1278
                    0,
1279
                    'rename',
1280
                    false,
1281
                    0
1282
                );
1283
                $path = substr($path, 7);
1284
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1285
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1286
            }
1287
        }
1288
1289
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1290
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1291
1292
        // TODO: htmlspecialchars to be checked for encoding related problems.
1293
        if ($same_parent && $same_previous) {
1294
            // Only update title and description.
1295
            $sql = "UPDATE $tbl_lp_item
1296
                    SET title = '".Database::escape_string($title)."',
1297
                        prerequisite = '".$prerequisites."',
1298
                        description = '".Database::escape_string($description)."'
1299
                        ".$audio_update_sql.",
1300
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1301
                    WHERE iid = $id";
1302
            Database::query($sql);
1303
        } else {
1304
            $old_parent = $row_select['parent_item_id'];
1305
            $old_previous = $row_select['previous_item_id'];
1306
            $old_next = $row_select['next_item_id'];
1307
            $old_order = $row_select['display_order'];
1308
            $old_prerequisite = $row_select['prerequisite'];
1309
            $old_max_time_allowed = $row_select['max_time_allowed'];
1310
1311
            /* BEGIN -- virtually remove the current item id */
1312
            /* for the next and previous item it is like the current item doesn't exist anymore */
1313
            if (0 != $old_previous) {
1314
                // Next
1315
                $sql = "UPDATE $tbl_lp_item
1316
                        SET next_item_id = $old_next
1317
                        WHERE iid = $old_previous";
1318
                Database::query($sql);
1319
            }
1320
1321
            if (!empty($old_next)) {
1322
                // Previous
1323
                $sql = "UPDATE $tbl_lp_item
1324
                        SET previous_item_id = $old_previous
1325
                        WHERE iid = $old_next";
1326
                Database::query($sql);
1327
            }
1328
1329
            // display_order - 1 for every item with a display_order
1330
            // bigger then the display_order of the current item.
1331
            $sql = "UPDATE $tbl_lp_item
1332
                    SET display_order = display_order - 1
1333
                    WHERE
1334
                        c_id = $course_id AND
1335
                        display_order > $old_order AND
1336
                        lp_id = ".$this->lp_id." AND
1337
                        parent_item_id = $old_parent";
1338
            Database::query($sql);
1339
            /* END -- virtually remove the current item id */
1340
1341
            /* BEGIN -- update the current item id to his new location */
1342
            if (0 == $previous) {
1343
                // Select the data of the item that should come after the current item.
1344
                $sql = "SELECT id, display_order
1345
                        FROM $tbl_lp_item
1346
                        WHERE
1347
                            c_id = $course_id AND
1348
                            lp_id = ".$this->lp_id." AND
1349
                            parent_item_id = $parent AND
1350
                            previous_item_id = $previous";
1351
                $res_select_old = Database::query($sql);
1352
                $row_select_old = Database::fetch_array($res_select_old);
1353
1354
                // If the new parent didn't have children before.
1355
                if (0 == Database::num_rows($res_select_old)) {
1356
                    $new_next = 0;
1357
                    $new_order = 1;
1358
                } else {
1359
                    $new_next = $row_select_old['id'];
1360
                    $new_order = $row_select_old['display_order'];
1361
                }
1362
            } else {
1363
                // Select the data of the item that should come before the current item.
1364
                $sql = "SELECT next_item_id, display_order
1365
                        FROM $tbl_lp_item
1366
                        WHERE iid = $previous";
1367
                $res_select_old = Database::query($sql);
1368
                $row_select_old = Database::fetch_array($res_select_old);
1369
                $new_next = $row_select_old['next_item_id'];
1370
                $new_order = $row_select_old['display_order'] + 1;
1371
            }
1372
1373
            // TODO: htmlspecialchars to be checked for encoding related problems.
1374
            // Update the current item with the new data.
1375
            $sql = "UPDATE $tbl_lp_item
1376
                    SET
1377
                        title = '".Database::escape_string($title)."',
1378
                        description = '".Database::escape_string($description)."',
1379
                        parent_item_id = $parent,
1380
                        previous_item_id = $previous,
1381
                        next_item_id = $new_next,
1382
                        display_order = $new_order
1383
                        $audio_update_sql
1384
                    WHERE iid = $id";
1385
            Database::query($sql);
1386
1387
            if (0 != $previous) {
1388
                // Update the previous item's next_item_id.
1389
                $sql = "UPDATE $tbl_lp_item
1390
                        SET next_item_id = $id
1391
                        WHERE iid = $previous";
1392
                Database::query($sql);
1393
            }
1394
1395
            if (!empty($new_next)) {
1396
                // Update the next item's previous_item_id.
1397
                $sql = "UPDATE $tbl_lp_item
1398
                        SET previous_item_id = $id
1399
                        WHERE iid = $new_next";
1400
                Database::query($sql);
1401
            }
1402
1403
            if ($old_prerequisite != $prerequisites) {
1404
                $sql = "UPDATE $tbl_lp_item
1405
                        SET prerequisite = '$prerequisites'
1406
                        WHERE iid = $id";
1407
                Database::query($sql);
1408
            }
1409
1410
            if ($old_max_time_allowed != $max_time_allowed) {
1411
                // update max time allowed
1412
                $sql = "UPDATE $tbl_lp_item
1413
                        SET max_time_allowed = $max_time_allowed
1414
                        WHERE iid = $id";
1415
                Database::query($sql);
1416
            }
1417
1418
            // Update all the items with the same or a bigger display_order than the current item.
1419
            $sql = "UPDATE $tbl_lp_item
1420
                    SET display_order = display_order + 1
1421
                    WHERE
1422
                       c_id = $course_id AND
1423
                       lp_id = ".$this->get_id()." AND
1424
                       iid <> $id AND
1425
                       parent_item_id = $parent AND
1426
                       display_order >= $new_order";
1427
            Database::query($sql);
1428
        }
1429
1430
        if ('link' == $row_select['item_type']) {
1431
            $link = new Link();
1432
            $linkId = $row_select['path'];
1433
            $link->updateLink($linkId, $url);
1434
        }
1435
    }
1436
1437
    /**
1438
     * Updates an item's prereq in place.
1439
     *
1440
     * @param int    $id              Element ID
1441
     * @param string $prerequisite_id Prerequisite Element ID
1442
     * @param int    $minScore        Prerequisite min score
1443
     * @param int    $maxScore        Prerequisite max score
1444
     *
1445
     * @return bool True on success, false on error
1446
     */
1447
    public function edit_item_prereq(
1448
        $id,
1449
        $prerequisite_id,
1450
        $minScore = 0,
1451
        $maxScore = 100
1452
    ) {
1453
        $id = (int) $id;
1454
        $prerequisite_id = (int) $prerequisite_id;
1455
1456
        if (empty($id)) {
1457
            return false;
1458
        }
1459
1460
        if (empty($minScore) || $minScore < 0) {
1461
            $minScore = 0;
1462
        }
1463
1464
        if (empty($maxScore) || $maxScore < 0) {
1465
            $maxScore = 100;
1466
        }
1467
1468
        $minScore = floatval($minScore);
1469
        $maxScore = floatval($maxScore);
1470
1471
        if (empty($prerequisite_id)) {
1472
            $prerequisite_id = 'NULL';
1473
            $minScore = 0;
1474
            $maxScore = 100;
1475
        }
1476
1477
        $table = Database::get_course_table(TABLE_LP_ITEM);
1478
        $sql = " UPDATE $table
1479
                 SET
1480
                    prerequisite = $prerequisite_id ,
1481
                    prerequisite_min_score = $minScore ,
1482
                    prerequisite_max_score = $maxScore
1483
                 WHERE iid = $id";
1484
        Database::query($sql);
1485
1486
        return true;
1487
    }
1488
1489
    /**
1490
     * Get the specific prefix index terms of this learning path.
1491
     *
1492
     * @param string $prefix
1493
     *
1494
     * @return array Array of terms
1495
     */
1496
    public function get_common_index_terms_by_prefix($prefix)
1497
    {
1498
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1499
        $terms = get_specific_field_values_list_by_prefix(
1500
            $prefix,
1501
            $this->cc,
1502
            TOOL_LEARNPATH,
1503
            $this->lp_id
1504
        );
1505
        $prefix_terms = [];
1506
        if (!empty($terms)) {
1507
            foreach ($terms as $term) {
1508
                $prefix_terms[] = $term['value'];
1509
            }
1510
        }
1511
1512
        return $prefix_terms;
1513
    }
1514
1515
    /**
1516
     * Gets the number of items currently completed.
1517
     *
1518
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1519
     *
1520
     * @return int The number of items currently completed
1521
     */
1522
    public function get_complete_items_count($failedStatusException = false)
1523
    {
1524
        $i = 0;
1525
        $completedStatusList = [
1526
            'completed',
1527
            'passed',
1528
            'succeeded',
1529
            'browsed',
1530
        ];
1531
1532
        if (!$failedStatusException) {
1533
            $completedStatusList[] = 'failed';
1534
        }
1535
1536
        foreach ($this->items as $id => $dummy) {
1537
            // Trying failed and browsed considered "progressed" as well.
1538
            if ($this->items[$id]->status_is($completedStatusList) &&
1539
                'dir' != $this->items[$id]->get_type()
1540
            ) {
1541
                $i++;
1542
            }
1543
        }
1544
1545
        return $i;
1546
    }
1547
1548
    /**
1549
     * Gets the current item ID.
1550
     *
1551
     * @return int The current learnpath item id
1552
     */
1553
    public function get_current_item_id()
1554
    {
1555
        $current = 0;
1556
        if (!empty($this->current)) {
1557
            $current = (int) $this->current;
1558
        }
1559
1560
        return $current;
1561
    }
1562
1563
    /**
1564
     * Force to get the first learnpath item id.
1565
     *
1566
     * @return int The current learnpath item id
1567
     */
1568
    public function get_first_item_id()
1569
    {
1570
        $current = 0;
1571
        if (is_array($this->ordered_items)) {
1572
            $current = $this->ordered_items[0];
1573
        }
1574
1575
        return $current;
1576
    }
1577
1578
    /**
1579
     * Gets the total number of items available for viewing in this SCORM.
1580
     *
1581
     * @return int The total number of items
1582
     */
1583
    public function get_total_items_count()
1584
    {
1585
        return count($this->items);
1586
    }
1587
1588
    /**
1589
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1590
     *
1591
     * @return int The total no-chapters number of items
1592
     */
1593
    public function getTotalItemsCountWithoutDirs()
1594
    {
1595
        $total = 0;
1596
        $typeListNotToCount = self::getChapterTypes();
1597
        foreach ($this->items as $temp2) {
1598
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1599
                $total++;
1600
            }
1601
        }
1602
1603
        return $total;
1604
    }
1605
1606
    /**
1607
     *  Sets the first element URL.
1608
     */
1609
    public function first()
1610
    {
1611
        if ($this->debug > 0) {
1612
            error_log('In learnpath::first()', 0);
1613
            error_log('$this->last_item_seen '.$this->last_item_seen);
1614
        }
1615
1616
        // Test if the last_item_seen exists and is not a dir.
1617
        if (0 == count($this->ordered_items)) {
1618
            $this->index = 0;
1619
        }
1620
1621
        if (!empty($this->last_item_seen) &&
1622
            !empty($this->items[$this->last_item_seen]) &&
1623
            'dir' != $this->items[$this->last_item_seen]->get_type()
1624
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1625
            //&& !$this->items[$this->last_item_seen]->is_done()
1626
        ) {
1627
            if ($this->debug > 2) {
1628
                error_log(
1629
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1630
                    $this->items[$this->last_item_seen]->get_type()
1631
                );
1632
            }
1633
            $index = -1;
1634
            foreach ($this->ordered_items as $myindex => $item_id) {
1635
                if ($item_id == $this->last_item_seen) {
1636
                    $index = $myindex;
1637
                    break;
1638
                }
1639
            }
1640
            if (-1 == $index) {
1641
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1642
                if ($this->debug > 2) {
1643
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1644
                }
1645
1646
                return false;
1647
            } else {
1648
                $this->last = $this->last_item_seen;
1649
                $this->current = $this->last_item_seen;
1650
                $this->index = $index;
1651
            }
1652
        } else {
1653
            if ($this->debug > 2) {
1654
                error_log('In learnpath::first() - No last item seen', 0);
1655
            }
1656
            $index = 0;
1657
            // Loop through all ordered items and stop at the first item that is
1658
            // not a directory *and* that has not been completed yet.
1659
            while (!empty($this->ordered_items[$index]) &&
1660
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1661
                (
1662
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1663
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1664
                ) && $index < $this->max_ordered_items) {
1665
                $index++;
1666
            }
1667
1668
            $this->last = $this->current;
1669
            // current is
1670
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1671
            $this->index = $index;
1672
            if ($this->debug > 2) {
1673
                error_log('$index '.$index);
1674
                error_log('In learnpath::first() - No last item seen');
1675
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1676
            }
1677
        }
1678
        if ($this->debug > 2) {
1679
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1680
        }
1681
    }
1682
1683
    /**
1684
     * Gets the js library from the database.
1685
     *
1686
     * @return string The name of the javascript library to be used
1687
     */
1688
    public function get_js_lib()
1689
    {
1690
        $lib = '';
1691
        if (!empty($this->js_lib)) {
1692
            $lib = $this->js_lib;
1693
        }
1694
1695
        return $lib;
1696
    }
1697
1698
    /**
1699
     * Gets the learnpath database ID.
1700
     *
1701
     * @return int Learnpath ID in the lp table
1702
     */
1703
    public function get_id()
1704
    {
1705
        if (!empty($this->lp_id)) {
1706
            return (int) $this->lp_id;
1707
        }
1708
1709
        return 0;
1710
    }
1711
1712
    /**
1713
     * Gets the last element URL.
1714
     *
1715
     * @return string URL to load into the viewer
1716
     */
1717
    public function get_last()
1718
    {
1719
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1720
        if (count($this->ordered_items) > 0) {
1721
            $this->index = count($this->ordered_items) - 1;
1722
1723
            return $this->ordered_items[$this->index];
1724
        }
1725
1726
        return false;
1727
    }
1728
1729
    /**
1730
     * Get the last element in the first level.
1731
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1732
     *
1733
     * @return mixed
1734
     */
1735
    public function getLastInFirstLevel()
1736
    {
1737
        try {
1738
            $lastId = Database::getManager()
1739
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1740
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1741
                ->setMaxResults(1)
1742
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1743
                ->getSingleScalarResult();
1744
1745
            return $lastId;
1746
        } catch (Exception $exception) {
1747
            return 0;
1748
        }
1749
    }
1750
1751
    /**
1752
     * Gets the navigation bar for the learnpath display screen.
1753
     *
1754
     * @param string $barId
1755
     *
1756
     * @return string The HTML string to use as a navigation bar
1757
     */
1758
    public function get_navigation_bar($barId = '')
1759
    {
1760
        if (empty($barId)) {
1761
            $barId = 'control-top';
1762
        }
1763
        $lpId = $this->lp_id;
1764
        $mycurrentitemid = $this->get_current_item_id();
1765
1766
        $reportingText = get_lang('Reporting');
1767
        $previousText = get_lang('Previous');
1768
        $nextText = get_lang('Next');
1769
        $fullScreenText = get_lang('Back to normal screen');
1770
1771
        $settings = api_get_configuration_value('lp_view_settings');
1772
        $display = isset($settings['display']) ? $settings['display'] : false;
1773
        $reportingIcon = '
1774
            <a class="icon-toolbar"
1775
                id="stats_link"
1776
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1777
                onclick="window.parent.API.save_asset(); return true;"
1778
                target="content_name" title="'.$reportingText.'">
1779
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1780
            </a>';
1781
1782
        if (!empty($display)) {
1783
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1784
            if (false === $showReporting) {
1785
                $reportingIcon = '';
1786
            }
1787
        }
1788
1789
        $hideArrows = false;
1790
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1791
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1792
        }
1793
1794
        $previousIcon = '';
1795
        $nextIcon = '';
1796
        if (false === $hideArrows) {
1797
            $previousIcon = '
1798
                <a class="icon-toolbar" id="scorm-previous" href="#"
1799
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1800
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1801
                </a>';
1802
1803
            $nextIcon = '
1804
                <a class="icon-toolbar" id="scorm-next" href="#"
1805
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1806
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1807
                </a>';
1808
        }
1809
1810
        if ('fullscreen' === $this->mode) {
1811
            $navbar = '
1812
                  <span id="'.$barId.'" class="buttons">
1813
                    '.$reportingIcon.'
1814
                    '.$previousIcon.'
1815
                    '.$nextIcon.'
1816
                    <a class="icon-toolbar" id="view-embedded"
1817
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1818
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1819
                    </a>
1820
                  </span>';
1821
        } else {
1822
            $navbar = '
1823
                 <span id="'.$barId.'" class="buttons text-right">
1824
                    '.$reportingIcon.'
1825
                    '.$previousIcon.'
1826
                    '.$nextIcon.'
1827
                </span>';
1828
        }
1829
1830
        return $navbar;
1831
    }
1832
1833
    /**
1834
     * Gets the next resource in queue (url).
1835
     *
1836
     * @return string URL to load into the viewer
1837
     */
1838
    public function get_next_index()
1839
    {
1840
        // TODO
1841
        $index = $this->index;
1842
        $index++;
1843
        while (
1844
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1845
            $index < $this->max_ordered_items
1846
        ) {
1847
            $index++;
1848
            if ($index == $this->max_ordered_items) {
1849
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1850
                    return $this->index;
1851
                }
1852
1853
                return $index;
1854
            }
1855
        }
1856
        if (empty($this->ordered_items[$index])) {
1857
            return $this->index;
1858
        }
1859
1860
        return $index;
1861
    }
1862
1863
    /**
1864
     * Gets item_id for the next element.
1865
     *
1866
     * @return int Next item (DB) ID
1867
     */
1868
    public function get_next_item_id()
1869
    {
1870
        $new_index = $this->get_next_index();
1871
        if (!empty($new_index)) {
1872
            if (isset($this->ordered_items[$new_index])) {
1873
                return $this->ordered_items[$new_index];
1874
            }
1875
        }
1876
1877
        return 0;
1878
    }
1879
1880
    /**
1881
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1882
     *
1883
     * Generally, the package provided is in the form of a zip file, so the function
1884
     * has been written to test a zip file. If not a zip, the function will return the
1885
     * default return value: ''
1886
     *
1887
     * @param string $file_path the path to the file
1888
     * @param string $file_name the original name of the file
1889
     *
1890
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1891
     */
1892
    public static function getPackageType($file_path, $file_name)
1893
    {
1894
        // Get name of the zip file without the extension.
1895
        $file_info = pathinfo($file_name);
1896
        $extension = $file_info['extension']; // Extension only.
1897
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1898
                'dll',
1899
                'exe',
1900
            ])) {
1901
            return 'oogie';
1902
        }
1903
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1904
                'dll',
1905
                'exe',
1906
            ])) {
1907
            return 'woogie';
1908
        }
1909
1910
        $zipFile = new PclZip($file_path);
1911
        // Check the zip content (real size and file extension).
1912
        $zipContentArray = $zipFile->listContent();
1913
        $package_type = '';
1914
        $manifest = '';
1915
        $aicc_match_crs = 0;
1916
        $aicc_match_au = 0;
1917
        $aicc_match_des = 0;
1918
        $aicc_match_cst = 0;
1919
        $countItems = 0;
1920
1921
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1922
        if (is_array($zipContentArray)) {
1923
            $countItems = count($zipContentArray);
1924
            if ($countItems > 0) {
1925
                foreach ($zipContentArray as $thisContent) {
1926
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1927
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1928
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1929
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1930
                        $package_type = 'scorm';
1931
                        break; // Exit the foreach loop.
1932
                    } elseif (
1933
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1934
                        in_array(
1935
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1936
                            ['crs', 'au', 'des', 'cst']
1937
                        )
1938
                    ) {
1939
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1940
                        switch ($ext) {
1941
                            case 'crs':
1942
                                $aicc_match_crs = 1;
1943
                                break;
1944
                            case 'au':
1945
                                $aicc_match_au = 1;
1946
                                break;
1947
                            case 'des':
1948
                                $aicc_match_des = 1;
1949
                                break;
1950
                            case 'cst':
1951
                                $aicc_match_cst = 1;
1952
                                break;
1953
                            default:
1954
                                break;
1955
                        }
1956
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1957
                    } else {
1958
                        $package_type = '';
1959
                    }
1960
                }
1961
            }
1962
        }
1963
1964
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1965
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1966
            $package_type = 'aicc';
1967
        }
1968
1969
        // Try with chamilo course builder
1970
        if (empty($package_type)) {
1971
            // Sometimes users will try to upload an empty zip, or a zip with
1972
            // only a folder. Catch that and make the calling function aware.
1973
            // If the single file was the imsmanifest.xml, then $package_type
1974
            // would be 'scorm' and we wouldn't be here.
1975
            if ($countItems < 2) {
1976
                return 'error-empty-package';
1977
            }
1978
            $package_type = 'chamilo';
1979
        }
1980
1981
        return $package_type;
1982
    }
1983
1984
    /**
1985
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1986
     *
1987
     * @return string URL to load into the viewer
1988
     */
1989
    public function get_previous_index()
1990
    {
1991
        $index = $this->index;
1992
        if (isset($this->ordered_items[$index - 1])) {
1993
            $index--;
1994
            while (isset($this->ordered_items[$index]) &&
1995
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1996
            ) {
1997
                $index--;
1998
                if ($index < 0) {
1999
                    return $this->index;
2000
                }
2001
            }
2002
        }
2003
2004
        return $index;
2005
    }
2006
2007
    /**
2008
     * Gets item_id for the next element.
2009
     *
2010
     * @return int Previous item (DB) ID
2011
     */
2012
    public function get_previous_item_id()
2013
    {
2014
        $index = $this->get_previous_index();
2015
2016
        return $this->ordered_items[$index];
2017
    }
2018
2019
    /**
2020
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2021
     *
2022
     * @param int    $lpItemId
2023
     * @param string $autostart
2024
     *
2025
     * @return string The mediaplayer HTML
2026
     */
2027
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2028
    {
2029
        $course_id = api_get_course_int_id();
2030
        $courseInfo = api_get_course_info();
2031
        $lpItemId = (int) $lpItemId;
2032
2033
        if (empty($courseInfo) || empty($lpItemId)) {
2034
            return '';
2035
        }
2036
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2037
2038
        if (empty($item)) {
2039
            return '';
2040
        }
2041
2042
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2043
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2044
        $itemViewId = (int) $item->db_item_view_id;
2045
2046
        // Getting all the information about the item.
2047
        $sql = "SELECT lp_view.status
2048
                FROM $tbl_lp_item as lpi
2049
                INNER JOIN $tbl_lp_item_view as lp_view
2050
                ON (lpi.iid = lp_view.lp_item_id)
2051
                WHERE
2052
                    lp_view.iid = $itemViewId AND
2053
                    lpi.iid = $lpItemId AND
2054
                    lp_view.c_id = $course_id";
2055
        $result = Database::query($sql);
2056
        $row = Database::fetch_assoc($result);
2057
        $output = '';
2058
        $audio = $item->audio;
2059
2060
        if (!empty($audio)) {
2061
            $list = $_SESSION['oLP']->get_toc();
2062
2063
            switch ($item->get_type()) {
2064
                case 'quiz':
2065
                    $type_quiz = false;
2066
                    foreach ($list as $toc) {
2067
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2068
                            $type_quiz = true;
2069
                        }
2070
                    }
2071
2072
                    if ($type_quiz) {
2073
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2074
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2075
                        } else {
2076
                            $autostart_audio = $autostart;
2077
                        }
2078
                    }
2079
                    break;
2080
                case TOOL_READOUT_TEXT:
2081
                    $autostart_audio = 'false';
2082
                    break;
2083
                default:
2084
                    $autostart_audio = 'true';
2085
            }
2086
2087
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2088
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2089
2090
            $player = Display::getMediaPlayer(
2091
                $file,
2092
                [
2093
                    'id' => 'lp_audio_media_player',
2094
                    'url' => $url,
2095
                    'autoplay' => $autostart_audio,
2096
                    'width' => '100%',
2097
                ]
2098
            );
2099
2100
            // The mp3 player.
2101
            $output = '<div id="container">';
2102
            $output .= $player;
2103
            $output .= '</div>';
2104
        }
2105
2106
        return $output;
2107
    }
2108
2109
    /**
2110
     * @param int   $studentId
2111
     * @param int   $prerequisite
2112
     * @param array $courseInfo
2113
     * @param int   $sessionId
2114
     *
2115
     * @return bool
2116
     */
2117
    public static function isBlockedByPrerequisite(
2118
        $studentId,
2119
        $prerequisite,
2120
        $courseInfo,
2121
        $sessionId
2122
    ) {
2123
        if (empty($courseInfo)) {
2124
            return false;
2125
        }
2126
2127
        $courseId = $courseInfo['real_id'];
2128
2129
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2130
        if ($allow) {
2131
            if (api_is_allowed_to_edit() ||
2132
                api_is_platform_admin(true) ||
2133
                api_is_drh() ||
2134
                api_is_coach($sessionId, $courseId, false)
2135
            ) {
2136
                return false;
2137
            }
2138
        }
2139
2140
        $isBlocked = false;
2141
        if (!empty($prerequisite)) {
2142
            $progress = self::getProgress(
2143
                $prerequisite,
2144
                $studentId,
2145
                $courseId,
2146
                $sessionId
2147
            );
2148
            if ($progress < 100) {
2149
                $isBlocked = true;
2150
            }
2151
2152
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2153
                // Block if it does not exceed minimum time
2154
                // Minimum time (in minutes) to pass the learning path
2155
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2156
2157
                if ($accumulateWorkTime > 0) {
2158
                    // Total time in course (sum of times in learning paths from course)
2159
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2160
2161
                    // Connect with the plugin_licences_course_session table
2162
                    // which indicates what percentage of the time applies
2163
                    // Minimum connection percentage
2164
                    $perc = 100;
2165
                    // Time from the course
2166
                    $tc = $accumulateWorkTimeTotal;
2167
2168
                    // Percentage of the learning paths
2169
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2170
                    // Minimum time for each learning path
2171
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2172
2173
                    // Spent time (in seconds) so far in the learning path
2174
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2175
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2176
2177
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2178
                        $isBlocked = true;
2179
                    }
2180
                }
2181
            }
2182
        }
2183
2184
        return $isBlocked;
2185
    }
2186
2187
    /**
2188
     * Checks if the learning path is visible for student after the progress
2189
     * of its prerequisite is completed, considering the time availability and
2190
     * the LP visibility.
2191
     *
2192
     * @param int   $lp_id
2193
     * @param int   $student_id
2194
     * @param array $courseInfo
2195
     * @param int   $sessionId
2196
     *
2197
     * @return bool
2198
     */
2199
    public static function is_lp_visible_for_student(
2200
        CLp $lp,
2201
        $student_id,
2202
        $courseInfo = [],
2203
        $sessionId = 0
2204
    ) {
2205
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2206
        $sessionId = (int) $sessionId;
2207
2208
        if (empty($courseInfo)) {
2209
            return false;
2210
        }
2211
2212
        if (empty($sessionId)) {
2213
            $sessionId = api_get_session_id();
2214
        }
2215
2216
        $courseId = $courseInfo['real_id'];
2217
2218
        /*$itemInfo = api_get_item_property_info(
2219
            $courseId,
2220
            TOOL_LEARNPATH,
2221
            $lp_id,
2222
            $sessionId
2223
        );*/
2224
2225
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2226
        // If the item was deleted.
2227
        if (false === $visibility) {
2228
            return false;
2229
        }
2230
2231
        $lp_id = $lp->getIid();
2232
        // @todo remove this query and load the row info as a parameter
2233
        $table = Database::get_course_table(TABLE_LP_MAIN);
2234
        // Get current prerequisite
2235
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2236
                FROM $table
2237
                WHERE iid = $lp_id";
2238
        $rs = Database::query($sql);
2239
        $now = time();
2240
        if (Database::num_rows($rs) > 0) {
2241
            $row = Database::fetch_array($rs, 'ASSOC');
2242
2243
            if (!empty($row['category_id'])) {
2244
                $em = Database::getManager();
2245
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2246
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2247
                    return false;
2248
                }
2249
            }
2250
2251
            $prerequisite = $row['prerequisite'];
2252
            $is_visible = true;
2253
2254
            $isBlocked = self::isBlockedByPrerequisite(
2255
                $student_id,
2256
                $prerequisite,
2257
                $courseInfo,
2258
                $sessionId
2259
            );
2260
2261
            if ($isBlocked) {
2262
                $is_visible = false;
2263
            }
2264
2265
            // Also check the time availability of the LP
2266
            if ($is_visible) {
2267
                // Adding visibility restrictions
2268
                if (!empty($row['publicated_on'])) {
2269
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2270
                        $is_visible = false;
2271
                    }
2272
                }
2273
                // Blocking empty start times see BT#2800
2274
                global $_custom;
2275
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2276
                    $_custom['lps_hidden_when_no_start_date']
2277
                ) {
2278
                    if (empty($row['publicated_on'])) {
2279
                        $is_visible = false;
2280
                    }
2281
                }
2282
2283
                if (!empty($row['expired_on'])) {
2284
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2285
                        $is_visible = false;
2286
                    }
2287
                }
2288
            }
2289
2290
            if ($is_visible) {
2291
                $subscriptionSettings = self::getSubscriptionSettings();
2292
2293
                // Check if the subscription users/group to a LP is ON
2294
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2295
                    true === $subscriptionSettings['allow_add_users_to_lp']
2296
                ) {
2297
                    // Try group
2298
                    $is_visible = false;
2299
                    // Checking only the user visibility
2300
                    $userVisibility = api_get_item_visibility(
2301
                        $courseInfo,
2302
                        'learnpath',
2303
                        $row['id'],
2304
                        $sessionId,
2305
                        $student_id,
2306
                        'LearnpathSubscription'
2307
                    );
2308
2309
                    if (1 == $userVisibility) {
2310
                        $is_visible = true;
2311
                    } else {
2312
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2313
                        if (!empty($userGroups)) {
2314
                            foreach ($userGroups as $groupInfo) {
2315
                                $groupId = $groupInfo['iid'];
2316
                                $userVisibility = api_get_item_visibility(
2317
                                    $courseInfo,
2318
                                    'learnpath',
2319
                                    $row['id'],
2320
                                    $sessionId,
2321
                                    null,
2322
                                    'LearnpathSubscription',
2323
                                    $groupId
2324
                                );
2325
2326
                                if (1 == $userVisibility) {
2327
                                    $is_visible = true;
2328
                                    break;
2329
                                }
2330
                            }
2331
                        }
2332
                    }
2333
                }
2334
            }
2335
2336
            return $is_visible;
2337
        }
2338
2339
        return false;
2340
    }
2341
2342
    /**
2343
     * @param int $lpId
2344
     * @param int $userId
2345
     * @param int $courseId
2346
     * @param int $sessionId
2347
     *
2348
     * @return int
2349
     */
2350
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2351
    {
2352
        $lpId = (int) $lpId;
2353
        $userId = (int) $userId;
2354
        $courseId = (int) $courseId;
2355
        $sessionId = (int) $sessionId;
2356
2357
        $sessionCondition = api_get_session_condition($sessionId);
2358
        $table = Database::get_course_table(TABLE_LP_VIEW);
2359
        $sql = "SELECT progress FROM $table
2360
                WHERE
2361
                    c_id = $courseId AND
2362
                    lp_id = $lpId AND
2363
                    user_id = $userId $sessionCondition ";
2364
        $res = Database::query($sql);
2365
2366
        $progress = 0;
2367
        if (Database::num_rows($res) > 0) {
2368
            $row = Database::fetch_array($res);
2369
            $progress = (int) $row['progress'];
2370
        }
2371
2372
        return $progress;
2373
    }
2374
2375
    /**
2376
     * @param array $lpList
2377
     * @param int   $userId
2378
     * @param int   $courseId
2379
     * @param int   $sessionId
2380
     *
2381
     * @return array
2382
     */
2383
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2384
    {
2385
        $lpList = array_map('intval', $lpList);
2386
        if (empty($lpList)) {
2387
            return [];
2388
        }
2389
2390
        $lpList = implode("','", $lpList);
2391
2392
        $userId = (int) $userId;
2393
        $courseId = (int) $courseId;
2394
        $sessionId = (int) $sessionId;
2395
2396
        $sessionCondition = api_get_session_condition($sessionId);
2397
        $table = Database::get_course_table(TABLE_LP_VIEW);
2398
        $sql = "SELECT lp_id, progress FROM $table
2399
                WHERE
2400
                    c_id = $courseId AND
2401
                    lp_id IN ('".$lpList."') AND
2402
                    user_id = $userId $sessionCondition ";
2403
        $res = Database::query($sql);
2404
2405
        if (Database::num_rows($res) > 0) {
2406
            $list = [];
2407
            while ($row = Database::fetch_array($res)) {
2408
                $list[$row['lp_id']] = $row['progress'];
2409
            }
2410
2411
            return $list;
2412
        }
2413
2414
        return [];
2415
    }
2416
2417
    /**
2418
     * Displays a progress bar
2419
     * completed so far.
2420
     *
2421
     * @param int    $percentage Progress value to display
2422
     * @param string $text_add   Text to display near the progress value
2423
     *
2424
     * @return string HTML string containing the progress bar
2425
     */
2426
    public static function get_progress_bar($percentage = -1, $text_add = '')
2427
    {
2428
        $text = $percentage.$text_add;
2429
        $output = '<div class="progress">
2430
            <div id="progress_bar_value"
2431
                class="progress-bar progress-bar-success" role="progressbar"
2432
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2433
            '.$text.'
2434
            </div>
2435
        </div>';
2436
2437
        return $output;
2438
    }
2439
2440
    /**
2441
     * @param string $mode can be '%' or 'abs'
2442
     *                     otherwise this value will be used $this->progress_bar_mode
2443
     *
2444
     * @return string
2445
     */
2446
    public function getProgressBar($mode = null)
2447
    {
2448
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2449
2450
        return self::get_progress_bar($percentage, $text_add);
2451
    }
2452
2453
    /**
2454
     * Gets the progress bar info to display inside the progress bar.
2455
     * Also used by scorm_api.php.
2456
     *
2457
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2458
     *                     we display a number of completed elements per total elements
2459
     * @param int    $add  Additional steps to fake as completed
2460
     *
2461
     * @return array Percentage or number and symbol (% or /xx)
2462
     */
2463
    public function get_progress_bar_text($mode = '', $add = 0)
2464
    {
2465
        if (empty($mode)) {
2466
            $mode = $this->progress_bar_mode;
2467
        }
2468
        $text = '';
2469
        $percentage = 0;
2470
        // If the option to use the score as progress is set for this learning
2471
        // path, then the rules are completely different: we assume only one
2472
        // item exists and the progress of the LP depends on the score
2473
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2474
        if ($scoreAsProgressSetting === true) {
2475
            $scoreAsProgress = $this->getUseScoreAsProgress();
2476
            if ($scoreAsProgress) {
2477
                // Get single item's score
2478
                $itemId = $this->get_current_item_id();
2479
                $item = $this->getItem($itemId);
2480
                $score = $item->get_score();
2481
                $maxScore = $item->get_max();
2482
                if ($mode = '%') {
2483
                    if (!empty($maxScore)) {
2484
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2485
                    }
2486
                    $percentage = number_format($percentage, 0);
2487
                    $text = '%';
2488
                } else {
2489
                    $percentage = $score;
2490
                    $text = '/'.$maxScore;
2491
                }
2492
2493
                return [$percentage, $text];
2494
            }
2495
        }
2496
        // otherwise just continue the normal processing of progress
2497
        $total_items = $this->getTotalItemsCountWithoutDirs();
2498
        $completeItems = $this->get_complete_items_count();
2499
        if ($add != 0) {
2500
            $completeItems += $add;
2501
        }
2502
        if ($completeItems > $total_items) {
2503
            $completeItems = $total_items;
2504
        }
2505
        if ($mode == '%') {
2506
            if ($total_items > 0) {
2507
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2508
            }
2509
            $percentage = number_format($percentage, 0);
2510
            $text = '%';
2511
        } elseif ($mode === 'abs') {
2512
            $percentage = $completeItems;
2513
            $text = '/'.$total_items;
2514
        }
2515
2516
        return [
2517
            $percentage,
2518
            $text,
2519
        ];
2520
    }
2521
2522
    /**
2523
     * Gets the progress bar mode.
2524
     *
2525
     * @return string The progress bar mode attribute
2526
     */
2527
    public function get_progress_bar_mode()
2528
    {
2529
        if (!empty($this->progress_bar_mode)) {
2530
            return $this->progress_bar_mode;
2531
        }
2532
2533
        return '%';
2534
    }
2535
2536
    /**
2537
     * Gets the learnpath theme (remote or local).
2538
     *
2539
     * @return string Learnpath theme
2540
     */
2541
    public function get_theme()
2542
    {
2543
        if (!empty($this->theme)) {
2544
            return $this->theme;
2545
        }
2546
2547
        return '';
2548
    }
2549
2550
    /**
2551
     * Gets the learnpath session id.
2552
     *
2553
     * @return int
2554
     */
2555
    public function get_lp_session_id()
2556
    {
2557
        if (!empty($this->lp_session_id)) {
2558
            return (int) $this->lp_session_id;
2559
        }
2560
2561
        return 0;
2562
    }
2563
2564
    /**
2565
     * Gets the learnpath image.
2566
     *
2567
     * @return string Web URL of the LP image
2568
     */
2569
    public function get_preview_image()
2570
    {
2571
        if (!empty($this->preview_image)) {
2572
            return $this->preview_image;
2573
        }
2574
2575
        return '';
2576
    }
2577
2578
    /**
2579
     * @param string $size
2580
     * @param string $path_type
2581
     *
2582
     * @return bool|string
2583
     */
2584
    public function get_preview_image_path($size = null, $path_type = 'web')
2585
    {
2586
        $preview_image = $this->get_preview_image();
2587
        if (isset($preview_image) && !empty($preview_image)) {
2588
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2589
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2590
2591
            if (isset($size)) {
2592
                $info = pathinfo($preview_image);
2593
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2594
2595
                if (file_exists($image_sys_path.$image_custom_size)) {
2596
                    if ('web' == $path_type) {
2597
                        return $image_path.$image_custom_size;
2598
                    } else {
2599
                        return $image_sys_path.$image_custom_size;
2600
                    }
2601
                }
2602
            } else {
2603
                if ('web' == $path_type) {
2604
                    return $image_path.$preview_image;
2605
                } else {
2606
                    return $image_sys_path.$preview_image;
2607
                }
2608
            }
2609
        }
2610
2611
        return false;
2612
    }
2613
2614
    /**
2615
     * Gets the learnpath author.
2616
     *
2617
     * @return string LP's author
2618
     */
2619
    public function get_author()
2620
    {
2621
        if (!empty($this->author)) {
2622
            return $this->author;
2623
        }
2624
2625
        return '';
2626
    }
2627
2628
    /**
2629
     * Gets hide table of contents.
2630
     *
2631
     * @return int
2632
     */
2633
    public function getHideTableOfContents()
2634
    {
2635
        return (int) $this->hide_toc_frame;
2636
    }
2637
2638
    /**
2639
     * Generate a new prerequisites string for a given item. If this item was a sco and
2640
     * its prerequisites were strings (instead of IDs), then transform those strings into
2641
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2642
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2643
     * same rule as the scormExport() method.
2644
     *
2645
     * @param int $item_id Item ID
2646
     *
2647
     * @return string Prerequisites string ready for the export as SCORM
2648
     */
2649
    public function get_scorm_prereq_string($item_id)
2650
    {
2651
        if ($this->debug > 0) {
2652
            error_log('In learnpath::get_scorm_prereq_string()');
2653
        }
2654
        if (!is_object($this->items[$item_id])) {
2655
            return false;
2656
        }
2657
        /** @var learnpathItem $oItem */
2658
        $oItem = $this->items[$item_id];
2659
        $prereq = $oItem->get_prereq_string();
2660
2661
        if (empty($prereq)) {
2662
            return '';
2663
        }
2664
        if (preg_match('/^\d+$/', $prereq) &&
2665
            isset($this->items[$prereq]) &&
2666
            is_object($this->items[$prereq])
2667
        ) {
2668
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2669
            // then simply return it (with the ITEM_ prefix).
2670
            //return 'ITEM_' . $prereq;
2671
            return $this->items[$prereq]->ref;
2672
        } else {
2673
            if (isset($this->refs_list[$prereq])) {
2674
                // It's a simple string item from which the ID can be found in the refs list,
2675
                // so we can transform it directly to an ID for export.
2676
                return $this->items[$this->refs_list[$prereq]]->ref;
2677
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2678
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2679
            } else {
2680
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2681
                // and replace them, one by one, by the internal IDs (chamilo db)
2682
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2683
                // by a space as well.
2684
                $find = [
2685
                    '&',
2686
                    '|',
2687
                    '~',
2688
                    '=',
2689
                    '<>',
2690
                    '{',
2691
                    '}',
2692
                    '*',
2693
                    '(',
2694
                    ')',
2695
                ];
2696
                $replace = [
2697
                    ' ',
2698
                    ' ',
2699
                    ' ',
2700
                    ' ',
2701
                    ' ',
2702
                    ' ',
2703
                    ' ',
2704
                    ' ',
2705
                    ' ',
2706
                    ' ',
2707
                ];
2708
                $prereq_mod = str_replace($find, $replace, $prereq);
2709
                $ids = explode(' ', $prereq_mod);
2710
                foreach ($ids as $id) {
2711
                    $id = trim($id);
2712
                    if (isset($this->refs_list[$id])) {
2713
                        $prereq = preg_replace(
2714
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2715
                            'ITEM_'.$this->refs_list[$id],
2716
                            $prereq
2717
                        );
2718
                    }
2719
                }
2720
2721
                return $prereq;
2722
            }
2723
        }
2724
    }
2725
2726
    /**
2727
     * Returns the XML DOM document's node.
2728
     *
2729
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2730
     * @param string   $id       The identifier to look for
2731
     *
2732
     * @return mixed The reference to the element found with that identifier. False if not found
2733
     */
2734
    public function get_scorm_xml_node(&$children, $id)
2735
    {
2736
        for ($i = 0; $i < $children->length; $i++) {
2737
            $item_temp = $children->item($i);
2738
            if ('item' == $item_temp->nodeName) {
2739
                if ($item_temp->getAttribute('identifier') == $id) {
2740
                    return $item_temp;
2741
                }
2742
            }
2743
            $subchildren = $item_temp->childNodes;
2744
            if ($subchildren && $subchildren->length > 0) {
2745
                $val = $this->get_scorm_xml_node($subchildren, $id);
2746
                if (is_object($val)) {
2747
                    return $val;
2748
                }
2749
            }
2750
        }
2751
2752
        return false;
2753
    }
2754
2755
    /**
2756
     * Gets the status list for all LP's items.
2757
     *
2758
     * @return array Array of [index] => [item ID => current status]
2759
     */
2760
    public function get_items_status_list()
2761
    {
2762
        $list = [];
2763
        foreach ($this->ordered_items as $item_id) {
2764
            $list[] = [
2765
                $item_id => $this->items[$item_id]->get_status(),
2766
            ];
2767
        }
2768
2769
        return $list;
2770
    }
2771
2772
    /**
2773
     * Return the number of interactions for the given learnpath Item View ID.
2774
     * This method can be used as static.
2775
     *
2776
     * @param int $lp_iv_id  Item View ID
2777
     * @param int $course_id course id
2778
     *
2779
     * @return int
2780
     */
2781
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2782
    {
2783
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2784
        $lp_iv_id = (int) $lp_iv_id;
2785
        $course_id = (int) $course_id;
2786
2787
        $sql = "SELECT count(*) FROM $table
2788
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2789
        $res = Database::query($sql);
2790
        $num = 0;
2791
        if (Database::num_rows($res)) {
2792
            $row = Database::fetch_array($res);
2793
            $num = $row[0];
2794
        }
2795
2796
        return $num;
2797
    }
2798
2799
    /**
2800
     * Return the interactions as an array for the given lp_iv_id.
2801
     * This method can be used as static.
2802
     *
2803
     * @param int $lp_iv_id Learnpath Item View ID
2804
     *
2805
     * @return array
2806
     *
2807
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2808
     */
2809
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2810
    {
2811
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2812
        $list = [];
2813
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2814
        $lp_iv_id = (int) $lp_iv_id;
2815
2816
        if (empty($lp_iv_id) || empty($course_id)) {
2817
            return [];
2818
        }
2819
2820
        $sql = "SELECT * FROM $table
2821
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2822
                ORDER BY order_id ASC";
2823
        $res = Database::query($sql);
2824
        $num = Database::num_rows($res);
2825
        if ($num > 0) {
2826
            $list[] = [
2827
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2828
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2829
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2830
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2831
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2832
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2833
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2834
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2835
                'student_response_formatted' => '',
2836
            ];
2837
            while ($row = Database::fetch_array($res)) {
2838
                $studentResponseFormatted = urldecode($row['student_response']);
2839
                $content_student_response = explode('__|', $studentResponseFormatted);
2840
                if (count($content_student_response) > 0) {
2841
                    if (count($content_student_response) >= 3) {
2842
                        // Pop the element off the end of array.
2843
                        array_pop($content_student_response);
2844
                    }
2845
                    $studentResponseFormatted = implode(',', $content_student_response);
2846
                }
2847
2848
                $list[] = [
2849
                    'order_id' => $row['order_id'] + 1,
2850
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2851
                    'type' => $row['interaction_type'],
2852
                    'time' => $row['completion_time'],
2853
                    'correct_responses' => '', // Hide correct responses from students.
2854
                    'student_response' => $row['student_response'],
2855
                    'result' => $row['result'],
2856
                    'latency' => $row['latency'],
2857
                    'student_response_formatted' => $studentResponseFormatted,
2858
                ];
2859
            }
2860
        }
2861
2862
        return $list;
2863
    }
2864
2865
    /**
2866
     * Return the number of objectives for the given learnpath Item View ID.
2867
     * This method can be used as static.
2868
     *
2869
     * @param int $lp_iv_id  Item View ID
2870
     * @param int $course_id Course ID
2871
     *
2872
     * @return int Number of objectives
2873
     */
2874
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2875
    {
2876
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2877
        $course_id = (int) $course_id;
2878
        $lp_iv_id = (int) $lp_iv_id;
2879
        $sql = "SELECT count(*) FROM $table
2880
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2881
        //@todo seems that this always returns 0
2882
        $res = Database::query($sql);
2883
        $num = 0;
2884
        if (Database::num_rows($res)) {
2885
            $row = Database::fetch_array($res);
2886
            $num = $row[0];
2887
        }
2888
2889
        return $num;
2890
    }
2891
2892
    /**
2893
     * Return the objectives as an array for the given lp_iv_id.
2894
     * This method can be used as static.
2895
     *
2896
     * @param int $lpItemViewId Learnpath Item View ID
2897
     * @param int $course_id
2898
     *
2899
     * @return array
2900
     *
2901
     * @todo    Translate labels
2902
     */
2903
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2904
    {
2905
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2906
        $lpItemViewId = (int) $lpItemViewId;
2907
2908
        if (empty($course_id) || empty($lpItemViewId)) {
2909
            return [];
2910
        }
2911
2912
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2913
        $sql = "SELECT * FROM $table
2914
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2915
                ORDER BY order_id ASC";
2916
        $res = Database::query($sql);
2917
        $num = Database::num_rows($res);
2918
        $list = [];
2919
        if ($num > 0) {
2920
            $list[] = [
2921
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2922
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2923
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2924
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2925
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2926
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2927
            ];
2928
            while ($row = Database::fetch_array($res)) {
2929
                $list[] = [
2930
                    'order_id' => $row['order_id'] + 1,
2931
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2932
                    'score_raw' => $row['score_raw'],
2933
                    'score_max' => $row['score_max'],
2934
                    'score_min' => $row['score_min'],
2935
                    'status' => $row['status'],
2936
                ];
2937
            }
2938
        }
2939
2940
        return $list;
2941
    }
2942
2943
    /**
2944
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2945
     * used by get_html_toc() to be ready to display.
2946
     *
2947
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2948
     */
2949
    public function get_toc()
2950
    {
2951
        $toc = [];
2952
        foreach ($this->ordered_items as $item_id) {
2953
            // TODO: Change this link generation and use new function instead.
2954
            $toc[] = [
2955
                'id' => $item_id,
2956
                'title' => $this->items[$item_id]->get_title(),
2957
                'status' => $this->items[$item_id]->get_status(),
2958
                'level' => $this->items[$item_id]->get_level(),
2959
                'type' => $this->items[$item_id]->get_type(),
2960
                'description' => $this->items[$item_id]->get_description(),
2961
                'path' => $this->items[$item_id]->get_path(),
2962
                'parent' => $this->items[$item_id]->get_parent(),
2963
            ];
2964
        }
2965
2966
        return $toc;
2967
    }
2968
2969
    /**
2970
     * Returns the CSS class name associated with a given item status.
2971
     *
2972
     * @param $status string an item status
2973
     *
2974
     * @return string CSS class name
2975
     */
2976
    public static function getStatusCSSClassName($status)
2977
    {
2978
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2979
            return self::STATUS_CSS_CLASS_NAME[$status];
2980
        }
2981
2982
        return '';
2983
    }
2984
2985
    /**
2986
     * Generate the tree of contents for this learnpath as an associative array tree
2987
     * with keys id, title, status, type, description, path, parent_id, children
2988
     * (title and descriptions as secured)
2989
     * and clues for CSS class composition:
2990
     *  - booleans is_current, is_parent_of_current, is_chapter
2991
     *  - string status_css_class_name.
2992
     *
2993
     * @param $parentId int restrict returned list to children of this parent
2994
     *
2995
     * @return array TOC as a table
2996
     */
2997
    public function getTOCTree($parentId = 0)
2998
    {
2999
        $toc = [];
3000
        $currentItemId = $this->get_current_item_id();
3001
3002
        foreach ($this->ordered_items as $itemId) {
3003
            $item = $this->items[$itemId];
3004
            if ($item->get_parent() == $parentId) {
3005
                $title = $item->get_title();
3006
                if (empty($title)) {
3007
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3008
                }
3009
3010
                $itemData = [
3011
                    'id' => $itemId,
3012
                    'title' => Security::remove_XSS($title),
3013
                    'status' => $item->get_status(),
3014
                    'level' => $item->get_level(), // FIXME should not be needed
3015
                    'type' => $item->get_type(),
3016
                    'description' => Security::remove_XSS($item->get_description()),
3017
                    'path' => $item->get_path(),
3018
                    'parent_id' => $item->get_parent(),
3019
                    'children' => $this->getTOCTree($itemId),
3020
                    'is_current' => ($itemId == $currentItemId),
3021
                    'is_parent_of_current' => false,
3022
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3023
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3024
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3025
                ];
3026
3027
                if (!empty($itemData['children'])) {
3028
                    foreach ($itemData['children'] as $child) {
3029
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3030
                            $itemData['is_parent_of_current'] = true;
3031
                            break;
3032
                        }
3033
                    }
3034
                }
3035
3036
                $toc[] = $itemData;
3037
            }
3038
        }
3039
3040
        return $toc;
3041
    }
3042
3043
    /**
3044
     * Generate and return the table of contents for this learnpath. The JS
3045
     * table returned is used inside of scorm_api.php.
3046
     *
3047
     * @param string $varname
3048
     *
3049
     * @return string A JS array variable construction
3050
     */
3051
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3052
    {
3053
        $toc = $varname.' = new Array();';
3054
        foreach ($this->ordered_items as $item_id) {
3055
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3056
        }
3057
3058
        return $toc;
3059
    }
3060
3061
    /**
3062
     * Gets the learning path type.
3063
     *
3064
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3065
     *
3066
     * @return mixed Type ID or name, depending on the parameter
3067
     */
3068
    public function get_type($get_name = false)
3069
    {
3070
        $res = false;
3071
        if (!empty($this->type) && (!$get_name)) {
3072
            $res = $this->type;
3073
        }
3074
3075
        return $res;
3076
    }
3077
3078
    /**
3079
     * Gets the learning path type as static method.
3080
     *
3081
     * @param int $lp_id
3082
     *
3083
     * @return mixed Type ID or name, depending on the parameter
3084
     */
3085
    public static function get_type_static($lp_id = 0)
3086
    {
3087
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3088
        $lp_id = (int) $lp_id;
3089
        $sql = "SELECT lp_type FROM $tbl_lp
3090
                WHERE iid = $lp_id";
3091
        $res = Database::query($sql);
3092
        if (false === $res) {
3093
            return null;
3094
        }
3095
        if (Database::num_rows($res) <= 0) {
3096
            return null;
3097
        }
3098
        $row = Database::fetch_array($res);
3099
3100
        return $row['lp_type'];
3101
    }
3102
3103
    /**
3104
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3105
     * This method can be used as abstract and is recursive.
3106
     *
3107
     * @param int $lp        Learnpath ID
3108
     * @param int $parent    Parent ID of the items to look for
3109
     * @param int $course_id
3110
     *
3111
     * @return array Ordered list of item IDs (empty array on error)
3112
     */
3113
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3114
    {
3115
        if (empty($course_id)) {
3116
            $course_id = api_get_course_int_id();
3117
        } else {
3118
            $course_id = (int) $course_id;
3119
        }
3120
        $list = [];
3121
3122
        if (empty($lp)) {
3123
            return $list;
3124
        }
3125
3126
        $lp = (int) $lp;
3127
        $parent = (int) $parent;
3128
3129
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3130
        $sql = "SELECT iid FROM $tbl_lp_item
3131
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3132
                ORDER BY display_order";
3133
3134
        $res = Database::query($sql);
3135
        while ($row = Database::fetch_array($res)) {
3136
            $sublist = self::get_flat_ordered_items_list(
3137
                $lp,
3138
                $row['iid'],
3139
                $course_id
3140
            );
3141
            $list[] = $row['iid'];
3142
            foreach ($sublist as $item) {
3143
                $list[] = $item;
3144
            }
3145
        }
3146
3147
        return $list;
3148
    }
3149
3150
    /**
3151
     * @return array
3152
     */
3153
    public static function getChapterTypes()
3154
    {
3155
        return [
3156
            'dir',
3157
        ];
3158
    }
3159
3160
    /**
3161
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3162
     *
3163
     * @param $tree
3164
     *
3165
     * @return array HTML TOC ready to display
3166
     */
3167
    public function getParentToc($tree)
3168
    {
3169
        if (empty($tree)) {
3170
            $tree = $this->get_toc();
3171
        }
3172
        $dirTypes = self::getChapterTypes();
3173
        $myCurrentId = $this->get_current_item_id();
3174
        $listParent = [];
3175
        $listChildren = [];
3176
        $listNotParent = [];
3177
        $list = [];
3178
        foreach ($tree as $subtree) {
3179
            if (in_array($subtree['type'], $dirTypes)) {
3180
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3181
                $subtree['children'] = $listChildren;
3182
                if (!empty($subtree['children'])) {
3183
                    foreach ($subtree['children'] as $subItem) {
3184
                        if ($subItem['id'] == $this->current) {
3185
                            $subtree['parent_current'] = 'in';
3186
                            $subtree['current'] = 'on';
3187
                        }
3188
                    }
3189
                }
3190
                $listParent[] = $subtree;
3191
            }
3192
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3193
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3194
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3195
                }
3196
3197
                $title = Security::remove_XSS($subtree['title']);
3198
                unset($subtree['title']);
3199
3200
                if (empty($title)) {
3201
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3202
                }
3203
                $classStyle = null;
3204
                if ($subtree['id'] == $this->current) {
3205
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3206
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3207
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3208
                }
3209
                $subtree['title'] = $title;
3210
                $subtree['class'] = $classStyle.' '.$cssStatus;
3211
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3212
                $subtree['current_id'] = $myCurrentId;
3213
                $listNotParent[] = $subtree;
3214
            }
3215
        }
3216
3217
        $list['are_parents'] = $listParent;
3218
        $list['not_parents'] = $listNotParent;
3219
3220
        return $list;
3221
    }
3222
3223
    /**
3224
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3225
     *
3226
     * @param array $tree
3227
     * @param int   $id
3228
     * @param bool  $parent
3229
     *
3230
     * @return array HTML TOC ready to display
3231
     */
3232
    public function getChildrenToc($tree, $id, $parent = true)
3233
    {
3234
        if (empty($tree)) {
3235
            $tree = $this->get_toc();
3236
        }
3237
3238
        $dirTypes = self::getChapterTypes();
3239
        $currentItemId = $this->get_current_item_id();
3240
        $list = [];
3241
3242
        foreach ($tree as $subtree) {
3243
            $subtree['tree'] = null;
3244
3245
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3246
                if ($subtree['id'] == $this->current) {
3247
                    $subtree['current'] = 'active';
3248
                } else {
3249
                    $subtree['current'] = null;
3250
                }
3251
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3252
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3253
                }
3254
3255
                $title = Security::remove_XSS($subtree['title']);
3256
                unset($subtree['title']);
3257
                if (empty($title)) {
3258
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3259
                }
3260
3261
                $classStyle = null;
3262
                if ($subtree['id'] == $this->current) {
3263
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3264
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3265
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3266
                }
3267
3268
                if (in_array($subtree['type'], $dirTypes)) {
3269
                    $subtree['title'] = stripslashes($title);
3270
                } else {
3271
                    $subtree['title'] = $title;
3272
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3273
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3274
                    $subtree['current_id'] = $currentItemId;
3275
                }
3276
                $list[] = $subtree;
3277
            }
3278
        }
3279
3280
        return $list;
3281
    }
3282
3283
    /**
3284
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3285
     *
3286
     * @param array $toc_list
3287
     *
3288
     * @return array HTML TOC ready to display
3289
     */
3290
    public function getListArrayToc($toc_list = [])
3291
    {
3292
        if (empty($toc_list)) {
3293
            $toc_list = $this->get_toc();
3294
        }
3295
        // Temporary variables.
3296
        $currentItemId = $this->get_current_item_id();
3297
        $list = [];
3298
        $arrayList = [];
3299
3300
        foreach ($toc_list as $item) {
3301
            $list['id'] = $item['id'];
3302
            $list['status'] = $item['status'];
3303
            $cssStatus = null;
3304
3305
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3306
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3307
            }
3308
3309
            $classStyle = ' ';
3310
            $dirTypes = self::getChapterTypes();
3311
3312
            if (in_array($item['type'], $dirTypes)) {
3313
                $classStyle = 'scorm_item_section ';
3314
            }
3315
            if ($item['id'] == $this->current) {
3316
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3317
            } elseif (!in_array($item['type'], $dirTypes)) {
3318
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3319
            }
3320
            $title = $item['title'];
3321
            if (empty($title)) {
3322
                $title = self::rl_get_resource_name(
3323
                    api_get_course_id(),
3324
                    $this->get_id(),
3325
                    $item['id']
3326
                );
3327
            }
3328
            $title = Security::remove_XSS($item['title']);
3329
3330
            if (empty($item['description'])) {
3331
                $list['description'] = $title;
3332
            } else {
3333
                $list['description'] = $item['description'];
3334
            }
3335
3336
            $list['class'] = $classStyle.' '.$cssStatus;
3337
            $list['level'] = $item['level'];
3338
            $list['type'] = $item['type'];
3339
3340
            if (in_array($item['type'], $dirTypes)) {
3341
                $list['css_level'] = 'level_'.$item['level'];
3342
            } else {
3343
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3344
            }
3345
3346
            if (in_array($item['type'], $dirTypes)) {
3347
                $list['title'] = stripslashes($title);
3348
            } else {
3349
                $list['title'] = stripslashes($title);
3350
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3351
                $list['current_id'] = $currentItemId;
3352
            }
3353
            $arrayList[] = $list;
3354
        }
3355
3356
        return $arrayList;
3357
    }
3358
3359
    /**
3360
     * Returns an HTML-formatted string ready to display with teacher buttons
3361
     * in LP view menu.
3362
     *
3363
     * @return string HTML TOC ready to display
3364
     */
3365
    public function get_teacher_toc_buttons()
3366
    {
3367
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3368
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3369
        $html = '';
3370
        if ($isAllow && false == $hideIcons) {
3371
            if ($this->get_lp_session_id() == api_get_session_id()) {
3372
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3373
                $html .= '<div class="btn-group">';
3374
                $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'>".
3375
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3376
                $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'>".
3377
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3378
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3379
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3380
                $html .= '</div>';
3381
                $html .= '</div>';
3382
            }
3383
        }
3384
3385
        return $html;
3386
    }
3387
3388
    /**
3389
     * Gets the learnpath maker name - generally the editor's name.
3390
     *
3391
     * @return string Learnpath maker name
3392
     */
3393
    public function get_maker()
3394
    {
3395
        if (!empty($this->maker)) {
3396
            return $this->maker;
3397
        }
3398
3399
        return '';
3400
    }
3401
3402
    /**
3403
     * Gets the learnpath name/title.
3404
     *
3405
     * @return string Learnpath name/title
3406
     */
3407
    public function get_name()
3408
    {
3409
        if (!empty($this->name)) {
3410
            return $this->name;
3411
        }
3412
3413
        return 'N/A';
3414
    }
3415
3416
    /**
3417
     * @return string
3418
     */
3419
    public function getNameNoTags()
3420
    {
3421
        return strip_tags($this->get_name());
3422
    }
3423
3424
    /**
3425
     * Gets a link to the resource from the present location, depending on item ID.
3426
     *
3427
     * @param string $type         Type of link expected
3428
     * @param int    $item_id      Learnpath item ID
3429
     * @param bool   $provided_toc
3430
     *
3431
     * @return string $provided_toc Link to the lp_item resource
3432
     */
3433
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3434
    {
3435
        $course_id = $this->get_course_int_id();
3436
        $item_id = (int) $item_id;
3437
3438
        if (empty($item_id)) {
3439
            $item_id = $this->get_current_item_id();
3440
3441
            if (empty($item_id)) {
3442
                //still empty, this means there was no item_id given and we are not in an object context or
3443
                //the object property is empty, return empty link
3444
                $this->first();
3445
3446
                return '';
3447
            }
3448
        }
3449
3450
        $file = '';
3451
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3452
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3453
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3454
3455
        $sql = "SELECT
3456
                    l.lp_type as ltype,
3457
                    l.path as lpath,
3458
                    li.item_type as litype,
3459
                    li.path as lipath,
3460
                    li.parameters as liparams
3461
        		FROM $lp_table l
3462
                INNER JOIN $lp_item_table li
3463
                ON (li.lp_id = l.iid)
3464
        		WHERE
3465
        		    li.iid = $item_id
3466
        		";
3467
        $res = Database::query($sql);
3468
        if (Database::num_rows($res) > 0) {
3469
            $row = Database::fetch_array($res);
3470
            $lp_type = $row['ltype'];
3471
            $lp_path = $row['lpath'];
3472
            $lp_item_type = $row['litype'];
3473
            $lp_item_path = $row['lipath'];
3474
            $lp_item_params = $row['liparams'];
3475
3476
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3477
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3478
            }
3479
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3480
            if ('http' === $type) {
3481
                //web path
3482
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3483
            } else {
3484
                //$course_path = $sys_course_path; //system path
3485
            }
3486
3487
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3488
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3489
            if (in_array(
3490
                $lp_item_type,
3491
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3492
            )
3493
            ) {
3494
                $lp_type = 1;
3495
            }
3496
3497
            // Now go through the specific cases to get the end of the path
3498
            // @todo Use constants instead of int values.
3499
            switch ($lp_type) {
3500
                case 1:
3501
                    $file = self::rl_get_resource_link_for_learnpath(
3502
                        $course_id,
3503
                        $this->get_id(),
3504
                        $item_id,
3505
                        $this->get_view_id()
3506
                    );
3507
                    switch ($lp_item_type) {
3508
                        case 'document':
3509
                            // Shows a button to download the file instead of just downloading the file directly.
3510
                            $documentPathInfo = pathinfo($file);
3511
                            if (isset($documentPathInfo['extension'])) {
3512
                                $parsed = parse_url($documentPathInfo['extension']);
3513
                                if (isset($parsed['path'])) {
3514
                                    $extension = $parsed['path'];
3515
                                    $extensionsToDownload = [
3516
                                        'zip',
3517
                                        'ppt',
3518
                                        'pptx',
3519
                                        'ods',
3520
                                        'xlsx',
3521
                                        'xls',
3522
                                        'csv',
3523
                                        'doc',
3524
                                        'docx',
3525
                                        'dot',
3526
                                    ];
3527
3528
                                    if (in_array($extension, $extensionsToDownload)) {
3529
                                        $file = api_get_path(WEB_CODE_PATH).
3530
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3531
                                    }
3532
                                }
3533
                            }
3534
                            break;
3535
                        case 'dir':
3536
                            $file = 'lp_content.php?type=dir';
3537
                            break;
3538
                        case 'link':
3539
                            if (Link::is_youtube_link($file)) {
3540
                                $src = Link::get_youtube_video_id($file);
3541
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3542
                            } elseif (Link::isVimeoLink($file)) {
3543
                                $src = Link::getVimeoLinkId($file);
3544
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3545
                            } else {
3546
                                // If the current site is HTTPS and the link is
3547
                                // HTTP, browsers will refuse opening the link
3548
                                $urlId = api_get_current_access_url_id();
3549
                                $url = api_get_access_url($urlId, false);
3550
                                $protocol = substr($url['url'], 0, 5);
3551
                                if ('https' === $protocol) {
3552
                                    $linkProtocol = substr($file, 0, 5);
3553
                                    if ('http:' === $linkProtocol) {
3554
                                        //this is the special intervention case
3555
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3556
                                    }
3557
                                }
3558
                            }
3559
                            break;
3560
                        case 'quiz':
3561
                            // Check how much attempts of a exercise exits in lp
3562
                            $lp_item_id = $this->get_current_item_id();
3563
                            $lp_view_id = $this->get_view_id();
3564
3565
                            $prevent_reinit = null;
3566
                            if (isset($this->items[$this->current])) {
3567
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3568
                            }
3569
3570
                            if (empty($provided_toc)) {
3571
                                $list = $this->get_toc();
3572
                            } else {
3573
                                $list = $provided_toc;
3574
                            }
3575
3576
                            $type_quiz = false;
3577
                            foreach ($list as $toc) {
3578
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3579
                                    $type_quiz = true;
3580
                                }
3581
                            }
3582
3583
                            if ($type_quiz) {
3584
                                $lp_item_id = (int) $lp_item_id;
3585
                                $lp_view_id = (int) $lp_view_id;
3586
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3587
                                        WHERE
3588
                                            c_id = $course_id AND
3589
                                            lp_item_id='".$lp_item_id."' AND
3590
                                            lp_view_id ='".$lp_view_id."' AND
3591
                                            status='completed'";
3592
                                $result = Database::query($sql);
3593
                                $row_count = Database:: fetch_row($result);
3594
                                $count_item_view = (int) $row_count[0];
3595
                                $not_multiple_attempt = 0;
3596
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3597
                                    $not_multiple_attempt = 1;
3598
                                }
3599
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3600
                            }
3601
                            break;
3602
                    }
3603
3604
                    $tmp_array = explode('/', $file);
3605
                    $document_name = $tmp_array[count($tmp_array) - 1];
3606
                    if (strpos($document_name, '_DELETED_')) {
3607
                        $file = 'blank.php?error=document_deleted';
3608
                    }
3609
                    break;
3610
                case 2:
3611
                    if ('dir' !== $lp_item_type) {
3612
                        // Quite complex here:
3613
                        // We want to make sure 'http://' (and similar) links can
3614
                        // be loaded as is (withouth the Chamilo path in front) but
3615
                        // some contents use this form: resource.htm?resource=http://blablabla
3616
                        // which means we have to find a protocol at the path's start, otherwise
3617
                        // it should not be considered as an external URL.
3618
                        // if ($this->prerequisites_match($item_id)) {
3619
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3620
                            if ($this->debug > 2) {
3621
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3622
                            }
3623
                            // Distant url, return as is.
3624
                            $file = $lp_item_path;
3625
                        } else {
3626
                            if ($this->debug > 2) {
3627
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3628
                            }
3629
                            // Prevent getting untranslatable urls.
3630
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3631
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3632
                            // Prepare the path.
3633
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3634
                            // TODO: Fix this for urls with protocol header.
3635
                            $file = str_replace('//', '/', $file);
3636
                            $file = str_replace(':/', '://', $file);
3637
                            if ('/' == substr($lp_path, -1)) {
3638
                                $lp_path = substr($lp_path, 0, -1);
3639
                            }
3640
3641
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3642
                                // if file not found.
3643
                                $decoded = html_entity_decode($lp_item_path);
3644
                                list($decoded) = explode('?', $decoded);
3645
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3646
                                    $file = self::rl_get_resource_link_for_learnpath(
3647
                                        $course_id,
3648
                                        $this->get_id(),
3649
                                        $item_id,
3650
                                        $this->get_view_id()
3651
                                    );
3652
                                    if (empty($file)) {
3653
                                        $file = 'blank.php?error=document_not_found';
3654
                                    } else {
3655
                                        $tmp_array = explode('/', $file);
3656
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3657
                                        if (strpos($document_name, '_DELETED_')) {
3658
                                            $file = 'blank.php?error=document_deleted';
3659
                                        } else {
3660
                                            $file = 'blank.php?error=document_not_found';
3661
                                        }
3662
                                    }
3663
                                } else {
3664
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3665
                                }
3666
                            }
3667
                        }
3668
3669
                        // We want to use parameters if they were defined in the imsmanifest
3670
                        if (false === strpos($file, 'blank.php')) {
3671
                            $lp_item_params = ltrim($lp_item_params, '?');
3672
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3673
                        }
3674
                    } else {
3675
                        $file = 'lp_content.php?type=dir';
3676
                    }
3677
                    break;
3678
                case 3:
3679
                    // Formatting AICC HACP append URL.
3680
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3681
                    if (!empty($lp_item_params)) {
3682
                        $aicc_append .= $lp_item_params.'&';
3683
                    }
3684
                    if ('dir' !== $lp_item_type) {
3685
                        // Quite complex here:
3686
                        // We want to make sure 'http://' (and similar) links can
3687
                        // be loaded as is (withouth the Chamilo path in front) but
3688
                        // some contents use this form: resource.htm?resource=http://blablabla
3689
                        // which means we have to find a protocol at the path's start, otherwise
3690
                        // it should not be considered as an external URL.
3691
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3692
                            if ($this->debug > 2) {
3693
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3694
                            }
3695
                            // Distant url, return as is.
3696
                            $file = $lp_item_path;
3697
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3698
                            /*
3699
                            if (stristr($file,'<servername>') !== false) {
3700
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3701
                            }
3702
                            */
3703
                            if (false !== stripos($file, '<servername>')) {
3704
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3705
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3706
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3707
                            }
3708
3709
                            $file .= $aicc_append;
3710
                        } else {
3711
                            if ($this->debug > 2) {
3712
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3713
                            }
3714
                            // Prevent getting untranslatable urls.
3715
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3716
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3717
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3718
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3719
                            // TODO: Fix this for urls with protocol header.
3720
                            $file = str_replace('//', '/', $file);
3721
                            $file = str_replace(':/', '://', $file);
3722
                            $file .= $aicc_append;
3723
                        }
3724
                    } else {
3725
                        $file = 'lp_content.php?type=dir';
3726
                    }
3727
                    break;
3728
                case 4:
3729
                    break;
3730
                default:
3731
                    break;
3732
            }
3733
            // Replace &amp; by & because &amp; will break URL with params
3734
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3735
        }
3736
        if ($this->debug > 2) {
3737
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3738
        }
3739
3740
        return $file;
3741
    }
3742
3743
    /**
3744
     * Gets the latest usable view or generate a new one.
3745
     *
3746
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3747
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3748
     *
3749
     * @return int DB lp_view id
3750
     */
3751
    public function get_view($attempt_num = 0, $userId = null)
3752
    {
3753
        $search = '';
3754
        // Use $attempt_num to enable multi-views management (disabled so far).
3755
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3756
            $search = 'AND view_count = '.$attempt_num;
3757
        }
3758
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3759
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3760
3761
        $course_id = api_get_course_int_id();
3762
        $sessionId = api_get_session_id();
3763
3764
        // Check user ID.
3765
        if (empty($userId)) {
3766
            if (empty($this->get_user_id())) {
3767
                $this->error = 'User ID is empty in learnpath::get_view()';
3768
3769
                return null;
3770
            } else {
3771
                $userId = $this->get_user_id();
3772
            }
3773
        }
3774
3775
        $sql = "SELECT iid, view_count FROM $lp_view_table
3776
        		WHERE
3777
        		    c_id = $course_id AND
3778
        		    lp_id = ".$this->get_id()." AND
3779
        		    user_id = ".$userId." AND
3780
        		    session_id = $sessionId
3781
        		    $search
3782
                ORDER BY view_count DESC";
3783
        $res = Database::query($sql);
3784
        if (Database::num_rows($res) > 0) {
3785
            $row = Database::fetch_array($res);
3786
            $this->lp_view_id = $row['iid'];
3787
        } elseif (!api_is_invitee()) {
3788
            // There is no database record, create one.
3789
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3790
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3791
            Database::query($sql);
3792
            $id = Database::insert_id();
3793
            $this->lp_view_id = $id;
3794
3795
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3796
            Database::query($sql);
3797
        }
3798
3799
        return $this->lp_view_id;
3800
    }
3801
3802
    /**
3803
     * Gets the current view id.
3804
     *
3805
     * @return int View ID (from lp_view)
3806
     */
3807
    public function get_view_id()
3808
    {
3809
        if (!empty($this->lp_view_id)) {
3810
            return (int) $this->lp_view_id;
3811
        }
3812
3813
        return 0;
3814
    }
3815
3816
    /**
3817
     * Gets the update queue.
3818
     *
3819
     * @return array Array containing IDs of items to be updated by JavaScript
3820
     */
3821
    public function get_update_queue()
3822
    {
3823
        return $this->update_queue;
3824
    }
3825
3826
    /**
3827
     * Gets the user ID.
3828
     *
3829
     * @return int User ID
3830
     */
3831
    public function get_user_id()
3832
    {
3833
        if (!empty($this->user_id)) {
3834
            return (int) $this->user_id;
3835
        }
3836
3837
        return false;
3838
    }
3839
3840
    /**
3841
     * Checks if any of the items has an audio element attached.
3842
     *
3843
     * @return bool True or false
3844
     */
3845
    public function has_audio()
3846
    {
3847
        $has = false;
3848
        foreach ($this->items as $i => $item) {
3849
            if (!empty($this->items[$i]->audio)) {
3850
                $has = true;
3851
                break;
3852
            }
3853
        }
3854
3855
        return $has;
3856
    }
3857
3858
    /**
3859
     * Moves an item up and down at its level.
3860
     *
3861
     * @param int    $id        Item to move up and down
3862
     * @param string $direction Direction 'up' or 'down'
3863
     *
3864
     * @return bool|int
3865
     */
3866
    public function move_item($id, $direction)
3867
    {
3868
        $course_id = api_get_course_int_id();
3869
        if (empty($id) || empty($direction)) {
3870
            return false;
3871
        }
3872
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3873
        $sql_sel = "SELECT *
3874
                    FROM $tbl_lp_item
3875
                    WHERE
3876
                        iid = $id
3877
                    ";
3878
        $res_sel = Database::query($sql_sel);
3879
        // Check if elem exists.
3880
        if (Database::num_rows($res_sel) < 1) {
3881
            return false;
3882
        }
3883
        // Gather data.
3884
        $row = Database::fetch_array($res_sel);
3885
        $previous = $row['previous_item_id'];
3886
        $next = $row['next_item_id'];
3887
        $display = $row['display_order'];
3888
        $parent = $row['parent_item_id'];
3889
        $lp = $row['lp_id'];
3890
        // Update the item (switch with previous/next one).
3891
        switch ($direction) {
3892
            case 'up':
3893
                if ($display > 1) {
3894
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3895
                                 WHERE iid = $previous";
3896
                    $res_sel2 = Database::query($sql_sel2);
3897
                    if (Database::num_rows($res_sel2) < 1) {
3898
                        $previous_previous = 0;
3899
                    }
3900
                    // Gather data.
3901
                    $row2 = Database::fetch_array($res_sel2);
3902
                    $previous_previous = $row2['previous_item_id'];
3903
                    // Update previous_previous item (switch "next" with current).
3904
                    if (0 != $previous_previous) {
3905
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3906
                                        next_item_id = $id
3907
                                    WHERE iid = $previous_previous";
3908
                        Database::query($sql_upd2);
3909
                    }
3910
                    // Update previous item (switch with current).
3911
                    if (0 != $previous) {
3912
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3913
                                    next_item_id = $next,
3914
                                    previous_item_id = $id,
3915
                                    display_order = display_order +1
3916
                                    WHERE iid = $previous";
3917
                        Database::query($sql_upd2);
3918
                    }
3919
3920
                    // Update current item (switch with previous).
3921
                    if (0 != $id) {
3922
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3923
                                        next_item_id = $previous,
3924
                                        previous_item_id = $previous_previous,
3925
                                        display_order = display_order-1
3926
                                    WHERE c_id = ".$course_id." AND id = $id";
3927
                        Database::query($sql_upd2);
3928
                    }
3929
                    // Update next item (new previous item).
3930
                    if (!empty($next)) {
3931
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3932
                                     WHERE iid = $next";
3933
                        Database::query($sql_upd2);
3934
                    }
3935
                    $display = $display - 1;
3936
                }
3937
                break;
3938
            case 'down':
3939
                if (0 != $next) {
3940
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3941
                                 WHERE iid = $next";
3942
                    $res_sel2 = Database::query($sql_sel2);
3943
                    if (Database::num_rows($res_sel2) < 1) {
3944
                        $next_next = 0;
3945
                    }
3946
                    // Gather data.
3947
                    $row2 = Database::fetch_array($res_sel2);
3948
                    $next_next = $row2['next_item_id'];
3949
                    // Update previous item (switch with current).
3950
                    if (0 != $previous) {
3951
                        $sql_upd2 = "UPDATE $tbl_lp_item
3952
                                     SET next_item_id = $next
3953
                                     WHERE iid = $previous";
3954
                        Database::query($sql_upd2);
3955
                    }
3956
                    // Update current item (switch with previous).
3957
                    if (0 != $id) {
3958
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3959
                                     previous_item_id = $next,
3960
                                     next_item_id = $next_next,
3961
                                     display_order = display_order + 1
3962
                                     WHERE iid = $id";
3963
                        Database::query($sql_upd2);
3964
                    }
3965
3966
                    // Update next item (new previous item).
3967
                    if (0 != $next) {
3968
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3969
                                     previous_item_id = $previous,
3970
                                     next_item_id = $id,
3971
                                     display_order = display_order-1
3972
                                     WHERE iid = $next";
3973
                        Database::query($sql_upd2);
3974
                    }
3975
3976
                    // Update next_next item (switch "previous" with current).
3977
                    if (0 != $next_next) {
3978
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3979
                                     previous_item_id = $id
3980
                                     WHERE iid = $next_next";
3981
                        Database::query($sql_upd2);
3982
                    }
3983
                    $display = $display + 1;
3984
                }
3985
                break;
3986
            default:
3987
                return false;
3988
        }
3989
3990
        return $display;
3991
    }
3992
3993
    /**
3994
     * Move a LP up (display_order).
3995
     *
3996
     * @param int $lp_id      Learnpath ID
3997
     * @param int $categoryId Category ID
3998
     *
3999
     * @return bool
4000
     */
4001
    public static function move_up($lp_id, $categoryId = 0)
4002
    {
4003
        $courseId = api_get_course_int_id();
4004
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4005
4006
        $categoryCondition = '';
4007
        if (!empty($categoryId)) {
4008
            $categoryId = (int) $categoryId;
4009
            $categoryCondition = " AND category_id = $categoryId";
4010
        }
4011
        $sql = "SELECT * FROM $lp_table
4012
                WHERE c_id = $courseId
4013
                $categoryCondition
4014
                ORDER BY display_order";
4015
        $res = Database::query($sql);
4016
        if (false === $res) {
4017
            return false;
4018
        }
4019
4020
        $lps = [];
4021
        $lp_order = [];
4022
        $num = Database::num_rows($res);
4023
        // First check the order is correct, globally (might be wrong because
4024
        // of versions < 1.8.4)
4025
        if ($num > 0) {
4026
            $i = 1;
4027
            while ($row = Database::fetch_array($res)) {
4028
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4029
                    $sql = "UPDATE $lp_table SET display_order = $i
4030
                            WHERE iid = ".$row['iid'];
4031
                    Database::query($sql);
4032
                }
4033
                $row['display_order'] = $i;
4034
                $lps[$row['iid']] = $row;
4035
                $lp_order[$i] = $row['iid'];
4036
                $i++;
4037
            }
4038
        }
4039
        if ($num > 1) { // If there's only one element, no need to sort.
4040
            $order = $lps[$lp_id]['display_order'];
4041
            if ($order > 1) { // If it's the first element, no need to move up.
4042
                $sql = "UPDATE $lp_table SET display_order = $order
4043
                        WHERE iid = ".$lp_order[$order - 1];
4044
                Database::query($sql);
4045
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4046
                        WHERE iid = $lp_id";
4047
                Database::query($sql);
4048
            }
4049
        }
4050
4051
        return true;
4052
    }
4053
4054
    /**
4055
     * Move a learnpath down (display_order).
4056
     *
4057
     * @param int $lp_id      Learnpath ID
4058
     * @param int $categoryId Category ID
4059
     *
4060
     * @return bool
4061
     */
4062
    public static function move_down($lp_id, $categoryId = 0)
4063
    {
4064
        $courseId = api_get_course_int_id();
4065
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4066
4067
        $categoryCondition = '';
4068
        if (!empty($categoryId)) {
4069
            $categoryId = (int) $categoryId;
4070
            $categoryCondition = " AND category_id = $categoryId";
4071
        }
4072
4073
        $sql = "SELECT * FROM $lp_table
4074
                WHERE c_id = $courseId
4075
                $categoryCondition
4076
                ORDER BY display_order";
4077
        $res = Database::query($sql);
4078
        if (false === $res) {
4079
            return false;
4080
        }
4081
        $lps = [];
4082
        $lp_order = [];
4083
        $num = Database::num_rows($res);
4084
        $max = 0;
4085
        // First check the order is correct, globally (might be wrong because
4086
        // of versions < 1.8.4).
4087
        if ($num > 0) {
4088
            $i = 1;
4089
            while ($row = Database::fetch_array($res)) {
4090
                $max = $i;
4091
                if ($row['display_order'] != $i) {
4092
                    // If we find a gap in the order, we need to fix it.
4093
                    $sql = "UPDATE $lp_table SET display_order = $i
4094
                              WHERE iid = ".$row['iid'];
4095
                    Database::query($sql);
4096
                }
4097
                $row['display_order'] = $i;
4098
                $lps[$row['iid']] = $row;
4099
                $lp_order[$i] = $row['iid'];
4100
                $i++;
4101
            }
4102
        }
4103
        if ($num > 1) { // If there's only one element, no need to sort.
4104
            $order = $lps[$lp_id]['display_order'];
4105
            if ($order < $max) { // If it's the first element, no need to move up.
4106
                $sql = "UPDATE $lp_table SET display_order = $order
4107
                        WHERE iid = ".$lp_order[$order + 1];
4108
                Database::query($sql);
4109
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4110
                        WHERE iid = $lp_id";
4111
                Database::query($sql);
4112
            }
4113
        }
4114
4115
        return true;
4116
    }
4117
4118
    /**
4119
     * Updates learnpath attributes to point to the next element
4120
     * The last part is similar to set_current_item but processing the other way around.
4121
     */
4122
    public function next()
4123
    {
4124
        if ($this->debug > 0) {
4125
            error_log('In learnpath::next()', 0);
4126
        }
4127
        $this->last = $this->get_current_item_id();
4128
        $this->items[$this->last]->save(
4129
            false,
4130
            $this->prerequisites_match($this->last)
4131
        );
4132
        $this->autocomplete_parents($this->last);
4133
        $new_index = $this->get_next_index();
4134
        if ($this->debug > 2) {
4135
            error_log('New index: '.$new_index, 0);
4136
        }
4137
        $this->index = $new_index;
4138
        if ($this->debug > 2) {
4139
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4140
        }
4141
        $this->current = $this->ordered_items[$new_index];
4142
        if ($this->debug > 2) {
4143
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4144
        }
4145
    }
4146
4147
    /**
4148
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4149
     * class, this might be redefined to allow several behaviours depending on the document type.
4150
     *
4151
     * @param int $id Resource ID
4152
     */
4153
    public function open($id)
4154
    {
4155
        // TODO:
4156
        // set the current resource attribute to this resource
4157
        // switch on element type (redefine in child class?)
4158
        // set status for this item to "opened"
4159
        // start timer
4160
        // initialise score
4161
        $this->index = 0; //or = the last item seen (see $this->last)
4162
    }
4163
4164
    /**
4165
     * Check that all prerequisites are fulfilled. Returns true and an
4166
     * empty string on success, returns false
4167
     * and the prerequisite string on error.
4168
     * This function is based on the rules for aicc_script language as
4169
     * described in the SCORM 1.2 CAM documentation page 108.
4170
     *
4171
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4172
     *
4173
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4174
     *              string otherwise
4175
     */
4176
    public function prerequisites_match($itemId = null)
4177
    {
4178
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4179
        if ($allow) {
4180
            if (api_is_allowed_to_edit() ||
4181
                api_is_platform_admin(true) ||
4182
                api_is_drh() ||
4183
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4184
            ) {
4185
                return true;
4186
            }
4187
        }
4188
4189
        $debug = $this->debug;
4190
        if ($debug > 0) {
4191
            error_log('In learnpath::prerequisites_match()');
4192
        }
4193
4194
        if (empty($itemId)) {
4195
            $itemId = $this->current;
4196
        }
4197
4198
        $currentItem = $this->getItem($itemId);
4199
4200
        if ($currentItem) {
4201
            if (2 == $this->type) {
4202
                // Getting prereq from scorm
4203
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4204
            } else {
4205
                $prereq_string = $currentItem->get_prereq_string();
4206
            }
4207
4208
            if (empty($prereq_string)) {
4209
                if ($debug > 0) {
4210
                    error_log('Found prereq_string is empty return true');
4211
                }
4212
4213
                return true;
4214
            }
4215
4216
            // Clean spaces.
4217
            $prereq_string = str_replace(' ', '', $prereq_string);
4218
            if ($debug > 0) {
4219
                error_log('Found prereq_string: '.$prereq_string, 0);
4220
            }
4221
4222
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4223
            $result = $currentItem->parse_prereq(
4224
                $prereq_string,
4225
                $this->items,
4226
                $this->refs_list,
4227
                $this->get_user_id()
4228
            );
4229
4230
            if (false === $result) {
4231
                $this->set_error_msg($currentItem->prereq_alert);
4232
            }
4233
        } else {
4234
            $result = true;
4235
            if ($debug > 1) {
4236
                error_log('$this->items['.$itemId.'] was not an object', 0);
4237
            }
4238
        }
4239
4240
        if ($debug > 1) {
4241
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4242
        }
4243
4244
        return $result;
4245
    }
4246
4247
    /**
4248
     * Updates learnpath attributes to point to the previous element
4249
     * The last part is similar to set_current_item but processing the other way around.
4250
     */
4251
    public function previous()
4252
    {
4253
        $this->last = $this->get_current_item_id();
4254
        $this->items[$this->last]->save(
4255
            false,
4256
            $this->prerequisites_match($this->last)
4257
        );
4258
        $this->autocomplete_parents($this->last);
4259
        $new_index = $this->get_previous_index();
4260
        $this->index = $new_index;
4261
        $this->current = $this->ordered_items[$new_index];
4262
    }
4263
4264
    /**
4265
     * Publishes a learnpath. This basically means show or hide the learnpath
4266
     * to normal users.
4267
     * Can be used as abstract.
4268
     *
4269
     * @param int $id         Learnpath ID
4270
     * @param int $visibility New visibility
4271
     *
4272
     * @return bool
4273
     */
4274
    public static function toggleVisibility($id, $visibility = 1)
4275
    {
4276
        $repo = Container::getLpRepository();
4277
        $lp = $repo->find($id);
4278
4279
        if (!$lp) {
4280
            return false;
4281
        }
4282
4283
        $visibility = (int) $visibility;
4284
4285
        if (1 === $visibility) {
4286
            $repo->setVisibilityPublished($lp);
4287
        } else {
4288
            $repo->setVisibilityDraft($lp);
4289
        }
4290
4291
        return true;
4292
4293
        /*$action = 'visible';
4294
        if (1 != $set_visibility) {
4295
            $action = 'invisible';
4296
            self::toggle_publish($lp_id, 'i');
4297
        }
4298
4299
        return api_item_property_update(
4300
            api_get_course_info(),
4301
            TOOL_LEARNPATH,
4302
            $lp_id,
4303
            $action,
4304
            api_get_user_id()
4305
        );*/
4306
    }
4307
4308
    /**
4309
     * Publishes a learnpath category.
4310
     * This basically means show or hide the learnpath category to normal users.
4311
     *
4312
     * @param int $id
4313
     * @param int $visibility
4314
     *
4315
     * @return bool
4316
     */
4317
    public static function toggleCategoryVisibility($id, $visibility = 1)
4318
    {
4319
        $repo = Container::getLpCategoryRepository();
4320
        $resource = $repo->find($id);
4321
4322
        if (!$resource) {
4323
            return false;
4324
        }
4325
4326
        $visibility = (int) $visibility;
4327
4328
        if (1 === $visibility) {
4329
            $repo->setVisibilityPublished($resource);
4330
        } else {
4331
            $repo->setVisibilityDraft($resource);
4332
            self::toggleCategoryPublish($id, 0);
4333
        }
4334
4335
        return false;
4336
        /*
4337
        $action = 'visible';
4338
        if (1 != $visibility) {
4339
            self::toggleCategoryPublish($id, 0);
4340
            $action = 'invisible';
4341
        }
4342
4343
        return api_item_property_update(
4344
            api_get_course_info(),
4345
            TOOL_LEARNPATH_CATEGORY,
4346
            $id,
4347
            $action,
4348
            api_get_user_id()
4349
        );*/
4350
    }
4351
4352
    /**
4353
     * Publishes a learnpath. This basically means show or hide the learnpath
4354
     * on the course homepage
4355
     * Can be used as abstract.
4356
     *
4357
     * @param int    $id            Learnpath id
4358
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4359
     *
4360
     * @return bool
4361
     */
4362
    public static function togglePublish($id, $setVisibility = 'v')
4363
    {
4364
        $addShortcut = false;
4365
        if ('v' === $setVisibility) {
4366
            $addShortcut = true;
4367
        }
4368
        $repo = Container::getLpRepository();
4369
        /** @var CLp $lp */
4370
        $lp = $repo->find($id);
4371
        $repoShortcut = Container::getShortcutRepository();
4372
        if ($addShortcut) {
4373
            $shortcut = new CShortcut();
4374
            $shortcut->setName($lp->getName());
4375
            $shortcut->setShortCutNode($lp->getResourceNode());
4376
4377
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4378
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4379
            $repoShortcut->getEntityManager()->flush();
4380
        } else {
4381
            $shortcut = $repoShortcut->getShortcutFromResource($lp);
4382
            if (null !== $shortcut) {
4383
                $repoShortcut->getEntityManager()->remove($shortcut);
4384
                $repoShortcut->getEntityManager()->flush();
4385
            }
4386
        }
4387
4388
        return true;
4389
        /*
4390
        $course_id = api_get_course_int_id();
4391
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4392
        $lp_id = (int) $lp_id;
4393
        $sql = "SELECT * FROM $tbl_lp
4394
                WHERE iid = $lp_id";
4395
        $result = Database::query($sql);
4396
4397
        if (Database::num_rows($result)) {
4398
            $row = Database::fetch_array($result);
4399
            $name = Database::escape_string($row['name']);
4400
            if ($set_visibility == 'i') {
4401
                $v = 0;
4402
            }
4403
            if ($set_visibility == 'v') {
4404
                $v = 1;
4405
            }
4406
4407
            $session_id = api_get_session_id();
4408
            $session_condition = api_get_session_condition($session_id);
4409
4410
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4411
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4412
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4413
4414
            $sql = "SELECT * FROM $tbl_tool
4415
                    WHERE
4416
                        c_id = $course_id AND
4417
                        (link = '$link' OR link = '$oldLink') AND
4418
                        image = 'scormbuilder.gif' AND
4419
                        (
4420
                            link LIKE '$link%' OR
4421
                            link LIKE '$oldLink%'
4422
                        )
4423
                        $session_condition
4424
                    ";
4425
4426
            $result = Database::query($sql);
4427
            $num = Database::num_rows($result);
4428
            if ($set_visibility == 'i' && $num > 0) {
4429
                $sql = "DELETE FROM $tbl_tool
4430
                        WHERE
4431
                            c_id = $course_id AND
4432
                            (link = '$link' OR link = '$oldLink') AND
4433
                            image='scormbuilder.gif'
4434
                            $session_condition";
4435
                Database::query($sql);
4436
            } elseif ($set_visibility == 'v' && $num == 0) {
4437
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4438
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4439
                Database::query($sql);
4440
                $insertId = Database::insert_id();
4441
                if ($insertId) {
4442
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4443
                    Database::query($sql);
4444
                }
4445
            } elseif ($set_visibility == 'v' && $num > 0) {
4446
                $sql = "UPDATE $tbl_tool SET
4447
                            c_id = $course_id,
4448
                            name = '$name',
4449
                            link = '$link',
4450
                            image = 'scormbuilder.gif',
4451
                            visibility = '$v',
4452
                            admin = '0',
4453
                            address = 'pastillegris.gif',
4454
                            added_tool = 0,
4455
                            session_id = $session_id
4456
                        WHERE
4457
                            c_id = ".$course_id." AND
4458
                            (link = '$link' OR link = '$oldLink') AND
4459
                            image='scormbuilder.gif'
4460
                            $session_condition
4461
                        ";
4462
                Database::query($sql);
4463
            } else {
4464
                // Parameter and database incompatible, do nothing, exit.
4465
                return false;
4466
            }
4467
        } else {
4468
            return false;
4469
        }*/
4470
    }
4471
4472
    /**
4473
     * Publishes a learnpath.
4474
     * Show or hide the learnpath category on the course homepage.
4475
     *
4476
     * @param int $id
4477
     * @param int $setVisibility
4478
     *
4479
     * @return bool
4480
     */
4481
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4482
    {
4483
        $setVisibility = (int) $setVisibility;
4484
        $sessionId = api_get_session_id();
4485
        $addShortcut = false;
4486
        if (1 === $setVisibility) {
4487
            $addShortcut = true;
4488
        }
4489
4490
        $repo = Container::getLpCategoryRepository();
4491
        /** @var CLpCategory $lp */
4492
        $category = $repo->find($id);
4493
        $repoShortcut = Container::getShortcutRepository();
4494
        if ($addShortcut) {
4495
            $shortcut = new CShortcut();
4496
            $shortcut->setName($category->getName());
4497
            $shortcut->setShortCutNode($category->getResourceNode());
4498
4499
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4500
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4501
            $repoShortcut->getEntityManager()->flush();
4502
        } else {
4503
            $shortcut = $repoShortcut->getShortcutFromResource($category);
4504
            if (null !== $shortcut) {
4505
                $repoShortcut->getEntityManager()->remove($shortcut);
4506
                $repoShortcut->getEntityManager()->flush();
4507
            }
4508
        }
4509
4510
        return true;
4511
4512
        $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...
4513
4514
        /** @var CLpCategory $category */
4515
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4516
4517
        if (!$category) {
4518
            return false;
4519
        }
4520
4521
        if (empty($courseId)) {
4522
            return false;
4523
        }
4524
4525
        $link = self::getCategoryLinkForTool($id);
4526
4527
        /** @var CTool $tool */
4528
        $tool = $em->createQuery("
4529
                SELECT t FROM ChamiloCourseBundle:CTool t
4530
                WHERE
4531
                    t.course = :course AND
4532
                    t.link = :link1 AND
4533
                    t.image LIKE 'lp_category.%' AND
4534
                    t.link LIKE :link2
4535
                    $sessionCondition
4536
            ")
4537
            ->setParameters([
4538
                'course' => $courseId,
4539
                'link1' => $link,
4540
                'link2' => "$link%",
4541
            ])
4542
            ->getOneOrNullResult();
4543
4544
        if (0 == $setVisibility && $tool) {
4545
            $em->remove($tool);
4546
            $em->flush();
4547
4548
            return true;
4549
        }
4550
4551
        if (1 == $setVisibility && !$tool) {
4552
            $tool = new CTool();
4553
            $tool
4554
                ->setCategory('authoring')
4555
                ->setCourse(api_get_course_entity($courseId))
4556
                ->setName(strip_tags($category->getName()))
4557
                ->setLink($link)
4558
                ->setImage('lp_category.png')
4559
                ->setVisibility(1)
4560
                ->setAdmin(0)
4561
                ->setAddress('pastillegris.gif')
4562
                ->setAddedTool(0)
4563
                ->setSessionId($sessionId)
4564
                ->setTarget('_self');
4565
4566
            $em->persist($tool);
4567
            $em->flush();
4568
4569
            $tool->setId($tool->getIid());
4570
4571
            $em->persist($tool);
4572
            $em->flush();
4573
4574
            return true;
4575
        }
4576
4577
        if (1 == $setVisibility && $tool) {
4578
            $tool
4579
                ->setName(strip_tags($category->getName()))
4580
                ->setVisibility(1);
4581
4582
            $em->persist($tool);
4583
            $em->flush();
4584
4585
            return true;
4586
        }
4587
4588
        return false;
4589
    }
4590
4591
    /**
4592
     * Check if the learnpath category is visible for a user.
4593
     *
4594
     * @param int
4595
     * @param int
4596
     *
4597
     * @return bool
4598
     */
4599
    public static function categoryIsVisibleForStudent(
4600
        CLpCategory $category,
4601
        User $user,
4602
        $courseId = 0,
4603
        $sessionId = 0
4604
    ) {
4605
        if (empty($category)) {
4606
            return false;
4607
        }
4608
4609
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4610
4611
        if ($isAllowedToEdit) {
4612
            return true;
4613
        }
4614
4615
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4616
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4617
4618
        $courseInfo = api_get_course_info_by_id($courseId);
4619
4620
        $categoryVisibility = api_get_item_visibility(
4621
            $courseInfo,
4622
            TOOL_LEARNPATH_CATEGORY,
4623
            $category->getId(),
4624
            $sessionId
4625
        );
4626
4627
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4628
            return false;
4629
        }
4630
4631
        $subscriptionSettings = self::getSubscriptionSettings();
4632
4633
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4634
            return true;
4635
        }
4636
4637
        $noUserSubscribed = false;
4638
        $noGroupSubscribed = true;
4639
        $users = $category->getUsers();
4640
        if (empty($users) || !$users->count()) {
4641
            $noUserSubscribed = true;
4642
        } elseif ($category->hasUserAdded($user)) {
4643
            return true;
4644
        }
4645
4646
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4647
            $em = Database::getManager();
4648
4649
            /** @var ItemPropertyRepository $itemRepo */
4650
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4651
4652
            /** @var CourseRepository $courseRepo */
4653
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4654
            $session = null;
4655
            if (!empty($sessionId)) {
4656
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4657
            }
4658
4659
                $course = $courseRepo->find($courseId);
4660
4661
        if ($courseId != 0) {
4662
                // Subscribed groups to a LP
4663
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4664
                    TOOL_LEARNPATH_CATEGORY,
4665
                    $category->getId(),
4666
                    $course,
4667
                    $session
4668
                );
4669
            }
4670
4671
            if (!empty($subscribedGroupsInLp)) {
4672
            $noGroupSubscribed = false;
4673
            if (!empty($groups)) {
4674
                $groups = array_column($groups, 'iid');
4675
                /** @var CItemProperty $item */
4676
                foreach ($subscribedGroupsInLp as $item) {
4677
                    if ($item->getGroup() &&
4678
                        in_array($item->getGroup()->getId(), $groups)
4679
                    ) {
4680
                        return true;
4681
                    }
4682
                }
4683
            }
4684
        }
4685
        $response = $noGroupSubscribed && $noUserSubscribed;
4686
4687
        return $response;
4688
    }
4689
4690
    /**
4691
     * Check if a learnpath category is published as course tool.
4692
     *
4693
     * @param int $courseId
4694
     *
4695
     * @return bool
4696
     */
4697
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4698
    {
4699
        return false;
4700
        $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...
4701
        $em = Database::getManager();
4702
4703
        $tools = $em
4704
            ->createQuery("
4705
                SELECT t FROM ChamiloCourseBundle:CTool t
4706
                WHERE t.course = :course AND
4707
                    t.name = :name AND
4708
                    t.image LIKE 'lp_category.%' AND
4709
                    t.link LIKE :link
4710
            ")
4711
            ->setParameters([
4712
                'course' => $courseId,
4713
                'name' => strip_tags($category->getName()),
4714
                'link' => "$link%",
4715
            ])
4716
            ->getResult();
4717
4718
        /** @var CTool $tool */
4719
        $tool = current($tools);
4720
4721
        return $tool ? $tool->getVisibility() : false;
4722
    }
4723
4724
    /**
4725
     * Restart the whole learnpath. Return the URL of the first element.
4726
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4727
     * To use a similar method  statically, use the create_new_attempt() method.
4728
     *
4729
     * @return bool
4730
     */
4731
    public function restart()
4732
    {
4733
        if ($this->debug > 0) {
4734
            error_log('In learnpath::restart()', 0);
4735
        }
4736
        // TODO
4737
        // Call autosave method to save the current progress.
4738
        //$this->index = 0;
4739
        if (api_is_invitee()) {
4740
            return false;
4741
        }
4742
        $session_id = api_get_session_id();
4743
        $course_id = api_get_course_int_id();
4744
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4745
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4746
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4747
        if ($this->debug > 2) {
4748
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4749
        }
4750
        Database::query($sql);
4751
        $view_id = Database::insert_id();
4752
4753
        if ($view_id) {
4754
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4755
            Database::query($sql);
4756
            $this->lp_view_id = $view_id;
4757
            $this->attempt = $this->attempt + 1;
4758
        } else {
4759
            $this->error = 'Could not insert into item_view table...';
4760
4761
            return false;
4762
        }
4763
        $this->autocomplete_parents($this->current);
4764
        foreach ($this->items as $index => $dummy) {
4765
            $this->items[$index]->restart();
4766
            $this->items[$index]->set_lp_view($this->lp_view_id);
4767
        }
4768
        $this->first();
4769
4770
        return true;
4771
    }
4772
4773
    /**
4774
     * Saves the current item.
4775
     *
4776
     * @return bool
4777
     */
4778
    public function save_current()
4779
    {
4780
        $debug = $this->debug;
4781
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4782
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4783
        if ($debug) {
4784
            error_log('save_current() saving item '.$this->current, 0);
4785
            error_log(''.print_r($this->items, true), 0);
4786
        }
4787
        if (isset($this->items[$this->current]) &&
4788
            is_object($this->items[$this->current])
4789
        ) {
4790
            if ($debug) {
4791
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4792
            }
4793
4794
            $res = $this->items[$this->current]->save(
4795
                false,
4796
                $this->prerequisites_match($this->current)
4797
            );
4798
            $this->autocomplete_parents($this->current);
4799
            $status = $this->items[$this->current]->get_status();
4800
            $this->update_queue[$this->current] = $status;
4801
4802
            if ($debug) {
4803
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4804
            }
4805
4806
            return $res;
4807
        }
4808
4809
        return false;
4810
    }
4811
4812
    /**
4813
     * Saves the given item.
4814
     *
4815
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4816
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4817
     *
4818
     * @return bool
4819
     */
4820
    public function save_item($item_id = null, $from_outside = true)
4821
    {
4822
        $debug = $this->debug;
4823
        if ($debug) {
4824
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4825
        }
4826
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4827
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4828
        if (empty($item_id)) {
4829
            $item_id = (int) $_REQUEST['id'];
4830
        }
4831
4832
        if (empty($item_id)) {
4833
            $item_id = $this->get_current_item_id();
4834
        }
4835
        if (isset($this->items[$item_id]) &&
4836
            is_object($this->items[$item_id])
4837
        ) {
4838
            if ($debug) {
4839
                error_log('Object exists');
4840
            }
4841
4842
            // Saving the item.
4843
            $res = $this->items[$item_id]->save(
4844
                $from_outside,
4845
                $this->prerequisites_match($item_id)
4846
            );
4847
4848
            if ($debug) {
4849
                error_log('update_queue before:');
4850
                error_log(print_r($this->update_queue, 1));
4851
            }
4852
            $this->autocomplete_parents($item_id);
4853
4854
            $status = $this->items[$item_id]->get_status();
4855
            $this->update_queue[$item_id] = $status;
4856
4857
            if ($debug) {
4858
                error_log('get_status(): '.$status);
4859
                error_log('update_queue after:');
4860
                error_log(print_r($this->update_queue, 1));
4861
            }
4862
4863
            return $res;
4864
        }
4865
4866
        return false;
4867
    }
4868
4869
    /**
4870
     * Saves the last item seen's ID only in case.
4871
     */
4872
    public function save_last()
4873
    {
4874
        $course_id = api_get_course_int_id();
4875
        $debug = $this->debug;
4876
        if ($debug) {
4877
            error_log('In learnpath::save_last()', 0);
4878
        }
4879
        $session_condition = api_get_session_condition(
4880
            api_get_session_id(),
4881
            true,
4882
            false
4883
        );
4884
        $table = Database::get_course_table(TABLE_LP_VIEW);
4885
4886
        $userId = $this->get_user_id();
4887
        if (empty($userId)) {
4888
            $userId = api_get_user_id();
4889
            if ($debug) {
4890
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4891
            }
4892
        }
4893
        if (isset($this->current) && !api_is_invitee()) {
4894
            if ($debug) {
4895
                error_log('Saving current item ('.$this->current.') for later review', 0);
4896
            }
4897
            $sql = "UPDATE $table SET
4898
                        last_item = ".$this->get_current_item_id()."
4899
                    WHERE
4900
                        c_id = $course_id AND
4901
                        lp_id = ".$this->get_id()." AND
4902
                        user_id = ".$userId." ".$session_condition;
4903
4904
            if ($debug) {
4905
                error_log('Saving last item seen : '.$sql, 0);
4906
            }
4907
            Database::query($sql);
4908
        }
4909
4910
        if (!api_is_invitee()) {
4911
            // Save progress.
4912
            list($progress) = $this->get_progress_bar_text('%');
4913
            if ($progress >= 0 && $progress <= 100) {
4914
                $progress = (int) $progress;
4915
                $sql = "UPDATE $table SET
4916
                            progress = $progress
4917
                        WHERE
4918
                            c_id = $course_id AND
4919
                            lp_id = ".$this->get_id()." AND
4920
                            user_id = ".$userId." ".$session_condition;
4921
                // Ignore errors as some tables might not have the progress field just yet.
4922
                Database::query($sql);
4923
                $this->progress_db = $progress;
4924
            }
4925
        }
4926
    }
4927
4928
    /**
4929
     * Sets the current item ID (checks if valid and authorized first).
4930
     *
4931
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4932
     */
4933
    public function set_current_item($item_id = null)
4934
    {
4935
        $debug = $this->debug;
4936
        if ($debug) {
4937
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4938
        }
4939
        if (empty($item_id)) {
4940
            if ($debug) {
4941
                error_log('No new current item given, ignore...', 0);
4942
            }
4943
            // Do nothing.
4944
        } else {
4945
            if ($debug) {
4946
                error_log('New current item given is '.$item_id.'...', 0);
4947
            }
4948
            if (is_numeric($item_id)) {
4949
                $item_id = (int) $item_id;
4950
                // TODO: Check in database here.
4951
                $this->last = $this->current;
4952
                $this->current = $item_id;
4953
                // TODO: Update $this->index as well.
4954
                foreach ($this->ordered_items as $index => $item) {
4955
                    if ($item == $this->current) {
4956
                        $this->index = $index;
4957
                        break;
4958
                    }
4959
                }
4960
                if ($debug) {
4961
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4962
                }
4963
            } else {
4964
                if ($debug) {
4965
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4966
                }
4967
            }
4968
        }
4969
    }
4970
4971
    /**
4972
     * Sets the encoding.
4973
     *
4974
     * @param string $enc New encoding
4975
     *
4976
     * @return bool
4977
     *
4978
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4979
     */
4980
    public function set_encoding($enc = 'UTF-8')
4981
    {
4982
        $enc = api_refine_encoding_id($enc);
4983
        if (empty($enc)) {
4984
            $enc = api_get_system_encoding();
4985
        }
4986
        if (api_is_encoding_supported($enc)) {
4987
            $lp = $this->get_id();
4988
            if (0 != $lp) {
4989
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4990
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4991
                        WHERE iid = ".$lp;
4992
                $res = Database::query($sql);
4993
4994
                return $res;
4995
            }
4996
        }
4997
4998
        return false;
4999
    }
5000
5001
    /**
5002
     * Sets the JS lib setting in the database directly.
5003
     * This is the JavaScript library file this lp needs to load on startup.
5004
     *
5005
     * @param string $lib Proximity setting
5006
     *
5007
     * @return bool True on update success. False otherwise.
5008
     */
5009
    public function set_jslib($lib = '')
5010
    {
5011
        $lp = $this->get_id();
5012
5013
        if (0 != $lp) {
5014
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5015
            $lib = Database::escape_string($lib);
5016
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5017
                    WHERE iid = $lp";
5018
            $res = Database::query($sql);
5019
5020
            return $res;
5021
        }
5022
5023
        return false;
5024
    }
5025
5026
    /**
5027
     * Sets the name of the LP maker (publisher) (and save).
5028
     *
5029
     * @param string $name Optional string giving the new content_maker of this learnpath
5030
     *
5031
     * @return bool True
5032
     */
5033
    public function set_maker($name = '')
5034
    {
5035
        if (empty($name)) {
5036
            return false;
5037
        }
5038
        $this->maker = $name;
5039
        $table = Database::get_course_table(TABLE_LP_MAIN);
5040
        $lp_id = $this->get_id();
5041
        $sql = "UPDATE $table SET
5042
                content_maker = '".Database::escape_string($this->maker)."'
5043
                WHERE iid = $lp_id";
5044
        Database::query($sql);
5045
5046
        return true;
5047
    }
5048
5049
    /**
5050
     * Sets the name of the current learnpath (and save).
5051
     *
5052
     * @param string $name Optional string giving the new name of this learnpath
5053
     *
5054
     * @return bool True/False
5055
     */
5056
    public function set_name($name = null)
5057
    {
5058
        if (empty($name)) {
5059
            return false;
5060
        }
5061
        $this->name = $name;
5062
5063
        $lp_id = $this->get_id();
5064
5065
        $repo = Container::getLpRepository();
5066
        /** @var CLp $lp */
5067
        $lp = $repo->find($lp_id);
5068
        $lp->setName($name);
5069
        $repo->updateNodeForResource($lp);
5070
5071
        /*
5072
        $course_id = $this->course_info['real_id'];
5073
        $sql = "UPDATE $lp_table SET
5074
            name = '$name'
5075
            WHERE iid = $lp_id";
5076
        $result = Database::query($sql);
5077
        // If the lp is visible on the homepage, change his name there.
5078
        if (Database::affected_rows($result)) {
5079
        $session_id = api_get_session_id();
5080
        $session_condition = api_get_session_condition($session_id);
5081
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5082
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5083
        $sql = "UPDATE $tbl_tool SET name = '$name'
5084
        	    WHERE
5085
        	        c_id = $course_id AND
5086
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5087
        Database::query($sql);*/
5088
5089
        //return true;
5090
        //}
5091
5092
        return false;
5093
    }
5094
5095
    /**
5096
     * Set index specified prefix terms for all items in this path.
5097
     *
5098
     * @param string $terms_string Comma-separated list of terms
5099
     * @param string $prefix       Xapian term prefix
5100
     *
5101
     * @return bool False on error, true otherwise
5102
     */
5103
    public function set_terms_by_prefix($terms_string, $prefix)
5104
    {
5105
        $course_id = api_get_course_int_id();
5106
        if ('true' !== api_get_setting('search_enabled')) {
5107
            return false;
5108
        }
5109
5110
        if (!extension_loaded('xapian')) {
5111
            return false;
5112
        }
5113
5114
        $terms_string = trim($terms_string);
5115
        $terms = explode(',', $terms_string);
5116
        array_walk($terms, 'trim_value');
5117
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5118
5119
        // Don't do anything if no change, verify only at DB, not the search engine.
5120
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5121
            return false;
5122
        }
5123
5124
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5125
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5126
5127
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5128
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5129
        $lp_id = (int) $_POST['lp_id'];
5130
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5131
        $result = Database::query($sql);
5132
        $di = new ChamiloIndexer();
5133
5134
        while ($lp_item = Database::fetch_array($result)) {
5135
            // Get search_did.
5136
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5137
            $sql = 'SELECT * FROM %s
5138
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5139
                    LIMIT 1';
5140
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5141
5142
            //echo $sql; echo '<br>';
5143
            $res = Database::query($sql);
5144
            if (Database::num_rows($res) > 0) {
5145
                $se_ref = Database::fetch_array($res);
5146
                // Compare terms.
5147
                $doc = $di->get_document($se_ref['search_did']);
5148
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5149
                $xterms = [];
5150
                foreach ($xapian_terms as $xapian_term) {
5151
                    $xterms[] = substr($xapian_term['name'], 1);
5152
                }
5153
5154
                $dterms = $terms;
5155
                $missing_terms = array_diff($dterms, $xterms);
5156
                $deprecated_terms = array_diff($xterms, $dterms);
5157
5158
                // Save it to search engine.
5159
                foreach ($missing_terms as $term) {
5160
                    $doc->add_term($prefix.$term, 1);
5161
                }
5162
                foreach ($deprecated_terms as $term) {
5163
                    $doc->remove_term($prefix.$term);
5164
                }
5165
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5166
                $di->getDb()->flush();
5167
            }
5168
        }
5169
5170
        return true;
5171
    }
5172
5173
    /**
5174
     * Sets the theme of the LP (local/remote) (and save).
5175
     *
5176
     * @param string $name Optional string giving the new theme of this learnpath
5177
     *
5178
     * @return bool Returns true if theme name is not empty
5179
     */
5180
    public function set_theme($name = '')
5181
    {
5182
        $this->theme = $name;
5183
        $table = Database::get_course_table(TABLE_LP_MAIN);
5184
        $lp_id = $this->get_id();
5185
        $sql = "UPDATE $table
5186
                SET theme = '".Database::escape_string($this->theme)."'
5187
                WHERE iid = $lp_id";
5188
        Database::query($sql);
5189
5190
        return true;
5191
    }
5192
5193
    /**
5194
     * Sets the image of an LP (and save).
5195
     *
5196
     * @param string $name Optional string giving the new image of this learnpath
5197
     *
5198
     * @return bool Returns true if theme name is not empty
5199
     */
5200
    public function set_preview_image($name = '')
5201
    {
5202
        $this->preview_image = $name;
5203
        $table = Database::get_course_table(TABLE_LP_MAIN);
5204
        $lp_id = $this->get_id();
5205
        $sql = "UPDATE $table SET
5206
                preview_image = '".Database::escape_string($this->preview_image)."'
5207
                WHERE iid = $lp_id";
5208
        Database::query($sql);
5209
5210
        return true;
5211
    }
5212
5213
    /**
5214
     * Sets the author of a LP (and save).
5215
     *
5216
     * @param string $name Optional string giving the new author of this learnpath
5217
     *
5218
     * @return bool Returns true if author's name is not empty
5219
     */
5220
    public function set_author($name = '')
5221
    {
5222
        $this->author = $name;
5223
        $table = Database::get_course_table(TABLE_LP_MAIN);
5224
        $lp_id = $this->get_id();
5225
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5226
                WHERE iid = $lp_id";
5227
        Database::query($sql);
5228
5229
        return true;
5230
    }
5231
5232
    /**
5233
     * Sets the hide_toc_frame parameter of a LP (and save).
5234
     *
5235
     * @param int $hide 1 if frame is hidden 0 then else
5236
     *
5237
     * @return bool Returns true if author's name is not empty
5238
     */
5239
    public function set_hide_toc_frame($hide)
5240
    {
5241
        if (intval($hide) == $hide) {
5242
            $this->hide_toc_frame = $hide;
5243
            $table = Database::get_course_table(TABLE_LP_MAIN);
5244
            $lp_id = $this->get_id();
5245
            $sql = "UPDATE $table SET
5246
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5247
                    WHERE iid = $lp_id";
5248
            Database::query($sql);
5249
5250
            return true;
5251
        }
5252
5253
        return false;
5254
    }
5255
5256
    /**
5257
     * Sets the prerequisite of a LP (and save).
5258
     *
5259
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5260
     *
5261
     * @return bool returns true if prerequisite is not empty
5262
     */
5263
    public function set_prerequisite($prerequisite)
5264
    {
5265
        $this->prerequisite = (int) $prerequisite;
5266
        $table = Database::get_course_table(TABLE_LP_MAIN);
5267
        $lp_id = $this->get_id();
5268
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5269
                WHERE iid = $lp_id";
5270
        Database::query($sql);
5271
5272
        return true;
5273
    }
5274
5275
    /**
5276
     * Sets the location/proximity of the LP (local/remote) (and save).
5277
     *
5278
     * @param string $name Optional string giving the new location of this learnpath
5279
     *
5280
     * @return bool True on success / False on error
5281
     */
5282
    public function set_proximity($name = '')
5283
    {
5284
        if (empty($name)) {
5285
            return false;
5286
        }
5287
5288
        $this->proximity = $name;
5289
        $table = Database::get_course_table(TABLE_LP_MAIN);
5290
        $lp_id = $this->get_id();
5291
        $sql = "UPDATE $table SET
5292
                    content_local = '".Database::escape_string($name)."'
5293
                WHERE iid = $lp_id";
5294
        Database::query($sql);
5295
5296
        return true;
5297
    }
5298
5299
    /**
5300
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5301
     *
5302
     * @param int $id DB ID of the item
5303
     */
5304
    public function set_previous_item($id)
5305
    {
5306
        if ($this->debug > 0) {
5307
            error_log('In learnpath::set_previous_item()', 0);
5308
        }
5309
        $this->last = $id;
5310
    }
5311
5312
    /**
5313
     * Sets use_max_score.
5314
     *
5315
     * @param int $use_max_score Optional string giving the new location of this learnpath
5316
     *
5317
     * @return bool True on success / False on error
5318
     */
5319
    public function set_use_max_score($use_max_score = 1)
5320
    {
5321
        $use_max_score = (int) $use_max_score;
5322
        $this->use_max_score = $use_max_score;
5323
        $table = Database::get_course_table(TABLE_LP_MAIN);
5324
        $lp_id = $this->get_id();
5325
        $sql = "UPDATE $table SET
5326
                    use_max_score = '".$this->use_max_score."'
5327
                WHERE iid = $lp_id";
5328
        Database::query($sql);
5329
5330
        return true;
5331
    }
5332
5333
    /**
5334
     * Sets and saves the expired_on date.
5335
     *
5336
     * @param string $expired_on Optional string giving the new author of this learnpath
5337
     *
5338
     * @throws \Doctrine\ORM\OptimisticLockException
5339
     *
5340
     * @return bool Returns true if author's name is not empty
5341
     */
5342
    public function set_expired_on($expired_on)
5343
    {
5344
        $em = Database::getManager();
5345
        /** @var CLp $lp */
5346
        $lp = $em
5347
            ->getRepository('ChamiloCourseBundle:CLp')
5348
            ->findOneBy(
5349
                [
5350
                    'iid' => $this->get_id(),
5351
                ]
5352
            );
5353
5354
        if (!$lp) {
5355
            return false;
5356
        }
5357
5358
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5359
5360
        $lp->setExpiredOn($this->expired_on);
5361
        $em->persist($lp);
5362
        $em->flush();
5363
5364
        return true;
5365
    }
5366
5367
    /**
5368
     * Sets and saves the publicated_on date.
5369
     *
5370
     * @param string $publicated_on Optional string giving the new author of this learnpath
5371
     *
5372
     * @throws \Doctrine\ORM\OptimisticLockException
5373
     *
5374
     * @return bool Returns true if author's name is not empty
5375
     */
5376
    public function set_publicated_on($publicated_on)
5377
    {
5378
        $em = Database::getManager();
5379
        /** @var CLp $lp */
5380
        $lp = $em
5381
            ->getRepository('ChamiloCourseBundle:CLp')
5382
            ->findOneBy(
5383
                [
5384
                    'iid' => $this->get_id(),
5385
                ]
5386
            );
5387
5388
        if (!$lp) {
5389
            return false;
5390
        }
5391
5392
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5393
        $lp->setPublicatedOn($this->publicated_on);
5394
        $em->persist($lp);
5395
        $em->flush();
5396
5397
        return true;
5398
    }
5399
5400
    /**
5401
     * Sets and saves the expired_on date.
5402
     *
5403
     * @return bool Returns true if author's name is not empty
5404
     */
5405
    public function set_modified_on()
5406
    {
5407
        $this->modified_on = api_get_utc_datetime();
5408
        $table = Database::get_course_table(TABLE_LP_MAIN);
5409
        $lp_id = $this->get_id();
5410
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5411
                WHERE iid = $lp_id";
5412
        Database::query($sql);
5413
5414
        return true;
5415
    }
5416
5417
    /**
5418
     * Sets the object's error message.
5419
     *
5420
     * @param string $error Error message. If empty, reinits the error string
5421
     */
5422
    public function set_error_msg($error = '')
5423
    {
5424
        if ($this->debug > 0) {
5425
            error_log('In learnpath::set_error_msg()', 0);
5426
        }
5427
        if (empty($error)) {
5428
            $this->error = '';
5429
        } else {
5430
            $this->error .= $error;
5431
        }
5432
    }
5433
5434
    /**
5435
     * Launches the current item if not 'sco'
5436
     * (starts timer and make sure there is a record ready in the DB).
5437
     *
5438
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5439
     *
5440
     * @return bool
5441
     */
5442
    public function start_current_item($allow_new_attempt = false)
5443
    {
5444
        $debug = $this->debug;
5445
        if ($debug) {
5446
            error_log('In learnpath::start_current_item()');
5447
            error_log('current: '.$this->current);
5448
        }
5449
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5450
            $type = $this->get_type();
5451
            $item_type = $this->items[$this->current]->get_type();
5452
            if ((2 == $type && 'sco' != $item_type) ||
5453
                (3 == $type && 'au' != $item_type) ||
5454
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5455
            ) {
5456
                if ($debug) {
5457
                    error_log('item type: '.$item_type);
5458
                    error_log('lp type: '.$type);
5459
                }
5460
                $this->items[$this->current]->open($allow_new_attempt);
5461
                $this->autocomplete_parents($this->current);
5462
                $prereq_check = $this->prerequisites_match($this->current);
5463
                if ($debug) {
5464
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5465
                }
5466
                $this->items[$this->current]->save(false, $prereq_check);
5467
            }
5468
            // If sco, then it is supposed to have been updated by some other call.
5469
            if ('sco' == $item_type) {
5470
                $this->items[$this->current]->restart();
5471
            }
5472
        }
5473
        if ($debug) {
5474
            error_log('lp_view_session_id');
5475
            error_log($this->lp_view_session_id);
5476
            error_log('api session id');
5477
            error_log(api_get_session_id());
5478
            error_log('End of learnpath::start_current_item()');
5479
        }
5480
5481
        return true;
5482
    }
5483
5484
    /**
5485
     * Stops the processing and counters for the old item (as held in $this->last).
5486
     *
5487
     * @return bool True/False
5488
     */
5489
    public function stop_previous_item()
5490
    {
5491
        $debug = $this->debug;
5492
        if ($debug) {
5493
            error_log('In learnpath::stop_previous_item()', 0);
5494
        }
5495
5496
        if (0 != $this->last && $this->last != $this->current &&
5497
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5498
        ) {
5499
            if ($debug) {
5500
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5501
            }
5502
            switch ($this->get_type()) {
5503
                case '3':
5504
                    if ('au' != $this->items[$this->last]->get_type()) {
5505
                        if ($debug) {
5506
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5507
                        }
5508
                        $this->items[$this->last]->close();
5509
                    } else {
5510
                        if ($debug) {
5511
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5512
                        }
5513
                    }
5514
                    break;
5515
                case '2':
5516
                    if ('sco' != $this->items[$this->last]->get_type()) {
5517
                        if ($debug) {
5518
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5519
                        }
5520
                        $this->items[$this->last]->close();
5521
                    } else {
5522
                        if ($debug) {
5523
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5524
                        }
5525
                    }
5526
                    break;
5527
                case '1':
5528
                default:
5529
                    if ($debug) {
5530
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5531
                    }
5532
                    $this->items[$this->last]->close();
5533
                    break;
5534
            }
5535
        } else {
5536
            if ($debug) {
5537
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5538
            }
5539
5540
            return false;
5541
        }
5542
5543
        return true;
5544
    }
5545
5546
    /**
5547
     * Updates the default view mode from fullscreen to embedded and inversely.
5548
     *
5549
     * @return string The current default view mode ('fullscreen' or 'embedded')
5550
     */
5551
    public function update_default_view_mode()
5552
    {
5553
        $table = Database::get_course_table(TABLE_LP_MAIN);
5554
        $sql = "SELECT * FROM $table
5555
                WHERE iid = ".$this->get_id();
5556
        $res = Database::query($sql);
5557
        if (Database::num_rows($res) > 0) {
5558
            $row = Database::fetch_array($res);
5559
            $default_view_mode = $row['default_view_mod'];
5560
            $view_mode = $default_view_mode;
5561
            switch ($default_view_mode) {
5562
                case 'fullscreen': // default with popup
5563
                    $view_mode = 'embedded';
5564
                    break;
5565
                case 'embedded': // default view with left menu
5566
                    $view_mode = 'embedframe';
5567
                    break;
5568
                case 'embedframe': //folded menu
5569
                    $view_mode = 'impress';
5570
                    break;
5571
                case 'impress':
5572
                    $view_mode = 'fullscreen';
5573
                    break;
5574
            }
5575
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5576
                    WHERE iid = ".$this->get_id();
5577
            Database::query($sql);
5578
            $this->mode = $view_mode;
5579
5580
            return $view_mode;
5581
        }
5582
5583
        return -1;
5584
    }
5585
5586
    /**
5587
     * Updates the default behaviour about auto-commiting SCORM updates.
5588
     *
5589
     * @return bool True if auto-commit has been set to 'on', false otherwise
5590
     */
5591
    public function update_default_scorm_commit()
5592
    {
5593
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5594
        $sql = "SELECT * FROM $lp_table
5595
                WHERE iid = ".$this->get_id();
5596
        $res = Database::query($sql);
5597
        if (Database::num_rows($res) > 0) {
5598
            $row = Database::fetch_array($res);
5599
            $force = $row['force_commit'];
5600
            if (1 == $force) {
5601
                $force = 0;
5602
                $force_return = false;
5603
            } elseif (0 == $force) {
5604
                $force = 1;
5605
                $force_return = true;
5606
            }
5607
            $sql = "UPDATE $lp_table SET force_commit = $force
5608
                    WHERE iid = ".$this->get_id();
5609
            Database::query($sql);
5610
            $this->force_commit = $force_return;
5611
5612
            return $force_return;
5613
        }
5614
5615
        return -1;
5616
    }
5617
5618
    /**
5619
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5620
     *
5621
     * @return bool True on success, false on failure
5622
     */
5623
    public function update_display_order()
5624
    {
5625
        $course_id = api_get_course_int_id();
5626
        $table = Database::get_course_table(TABLE_LP_MAIN);
5627
        $sql = "SELECT * FROM $table
5628
                WHERE c_id = $course_id
5629
                ORDER BY display_order";
5630
        $res = Database::query($sql);
5631
        if (false === $res) {
5632
            return false;
5633
        }
5634
5635
        $num = Database::num_rows($res);
5636
        // First check the order is correct, globally (might be wrong because
5637
        // of versions < 1.8.4).
5638
        if ($num > 0) {
5639
            $i = 1;
5640
            while ($row = Database::fetch_array($res)) {
5641
                if ($row['display_order'] != $i) {
5642
                    // If we find a gap in the order, we need to fix it.
5643
                    $sql = "UPDATE $table SET display_order = $i
5644
                            WHERE iid = ".$row['iid'];
5645
                    Database::query($sql);
5646
                }
5647
                $i++;
5648
            }
5649
        }
5650
5651
        return true;
5652
    }
5653
5654
    /**
5655
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5656
     *
5657
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5658
     */
5659
    public function update_reinit()
5660
    {
5661
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5662
        $sql = "SELECT * FROM $lp_table
5663
                WHERE iid = ".$this->get_id();
5664
        $res = Database::query($sql);
5665
        if (Database::num_rows($res) > 0) {
5666
            $row = Database::fetch_array($res);
5667
            $force = $row['prevent_reinit'];
5668
            if (1 == $force) {
5669
                $force = 0;
5670
            } elseif (0 == $force) {
5671
                $force = 1;
5672
            }
5673
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5674
                    WHERE iid = ".$this->get_id();
5675
            Database::query($sql);
5676
            $this->prevent_reinit = $force;
5677
5678
            return $force;
5679
        }
5680
5681
        return -1;
5682
    }
5683
5684
    /**
5685
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5686
     *
5687
     * @return string 'single', 'multi' or 'seriousgame'
5688
     *
5689
     * @author ndiechburg <[email protected]>
5690
     */
5691
    public function get_attempt_mode()
5692
    {
5693
        //Set default value for seriousgame_mode
5694
        if (!isset($this->seriousgame_mode)) {
5695
            $this->seriousgame_mode = 0;
5696
        }
5697
        // Set default value for prevent_reinit
5698
        if (!isset($this->prevent_reinit)) {
5699
            $this->prevent_reinit = 1;
5700
        }
5701
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5702
            return 'seriousgame';
5703
        }
5704
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5705
            return 'single';
5706
        }
5707
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5708
            return 'multiple';
5709
        }
5710
5711
        return 'single';
5712
    }
5713
5714
    /**
5715
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5716
     *
5717
     * @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...
5718
     *
5719
     * @return bool
5720
     *
5721
     * @author ndiechburg <[email protected]>
5722
     */
5723
    public function set_attempt_mode($mode)
5724
    {
5725
        switch ($mode) {
5726
            case 'seriousgame':
5727
                $sg_mode = 1;
5728
                $prevent_reinit = 1;
5729
                break;
5730
            case 'single':
5731
                $sg_mode = 0;
5732
                $prevent_reinit = 1;
5733
                break;
5734
            case 'multiple':
5735
                $sg_mode = 0;
5736
                $prevent_reinit = 0;
5737
                break;
5738
            default:
5739
                $sg_mode = 0;
5740
                $prevent_reinit = 0;
5741
                break;
5742
        }
5743
        $this->prevent_reinit = $prevent_reinit;
5744
        $this->seriousgame_mode = $sg_mode;
5745
        $table = Database::get_course_table(TABLE_LP_MAIN);
5746
        $sql = "UPDATE $table SET
5747
                prevent_reinit = $prevent_reinit ,
5748
                seriousgame_mode = $sg_mode
5749
                WHERE iid = ".$this->get_id();
5750
        $res = Database::query($sql);
5751
        if ($res) {
5752
            return true;
5753
        } else {
5754
            return false;
5755
        }
5756
    }
5757
5758
    /**
5759
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5760
     *
5761
     * @author ndiechburg <[email protected]>
5762
     */
5763
    public function switch_attempt_mode()
5764
    {
5765
        $mode = $this->get_attempt_mode();
5766
        switch ($mode) {
5767
            case 'single':
5768
                $next_mode = 'multiple';
5769
                break;
5770
            case 'multiple':
5771
                $next_mode = 'seriousgame';
5772
                break;
5773
            case 'seriousgame':
5774
            default:
5775
                $next_mode = 'single';
5776
                break;
5777
        }
5778
        $this->set_attempt_mode($next_mode);
5779
    }
5780
5781
    /**
5782
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5783
     * but possibility to do again a completed item.
5784
     *
5785
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5786
     *
5787
     * @author ndiechburg <[email protected]>
5788
     */
5789
    public function set_seriousgame_mode()
5790
    {
5791
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5792
        $sql = "SELECT * FROM $lp_table
5793
                WHERE iid = ".$this->get_id();
5794
        $res = Database::query($sql);
5795
        if (Database::num_rows($res) > 0) {
5796
            $row = Database::fetch_array($res);
5797
            $force = $row['seriousgame_mode'];
5798
            if (1 == $force) {
5799
                $force = 0;
5800
            } elseif (0 == $force) {
5801
                $force = 1;
5802
            }
5803
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5804
			        WHERE iid = ".$this->get_id();
5805
            Database::query($sql);
5806
            $this->seriousgame_mode = $force;
5807
5808
            return $force;
5809
        }
5810
5811
        return -1;
5812
    }
5813
5814
    /**
5815
     * Updates the "scorm_debug" value that shows or hide the debug window.
5816
     *
5817
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5818
     */
5819
    public function update_scorm_debug()
5820
    {
5821
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5822
        $sql = "SELECT * FROM $lp_table
5823
                WHERE iid = ".$this->get_id();
5824
        $res = Database::query($sql);
5825
        if (Database::num_rows($res) > 0) {
5826
            $row = Database::fetch_array($res);
5827
            $force = $row['debug'];
5828
            if (1 == $force) {
5829
                $force = 0;
5830
            } elseif (0 == $force) {
5831
                $force = 1;
5832
            }
5833
            $sql = "UPDATE $lp_table SET debug = $force
5834
                    WHERE iid = ".$this->get_id();
5835
            Database::query($sql);
5836
            $this->scorm_debug = $force;
5837
5838
            return $force;
5839
        }
5840
5841
        return -1;
5842
    }
5843
5844
    /**
5845
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5846
     *
5847
     * @author Kevin Van Den Haute
5848
     *
5849
     * @param  array
5850
     */
5851
    public function tree_array($array)
5852
    {
5853
        $array = $this->sort_tree_array($array);
5854
        $this->create_tree_array($array);
5855
    }
5856
5857
    /**
5858
     * Creates an array with the elements of the learning path tree in it.
5859
     *
5860
     * @author Kevin Van Den Haute
5861
     *
5862
     * @param array $array
5863
     * @param int   $parent
5864
     * @param int   $depth
5865
     * @param array $tmp
5866
     */
5867
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5868
    {
5869
        if (is_array($array)) {
5870
            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...
5871
                if ($array[$i]['parent_item_id'] == $parent) {
5872
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5873
                        $tmp[] = $array[$i]['parent_item_id'];
5874
                        $depth++;
5875
                    }
5876
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5877
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5878
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5879
5880
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5881
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5882
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5883
                    $this->arrMenu[] = [
5884
                        'id' => $array[$i]['id'],
5885
                        'ref' => $ref,
5886
                        'item_type' => $array[$i]['item_type'],
5887
                        'title' => $array[$i]['title'],
5888
                        'title_raw' => $array[$i]['title_raw'],
5889
                        'path' => $path,
5890
                        'description' => $array[$i]['description'],
5891
                        'parent_item_id' => $array[$i]['parent_item_id'],
5892
                        'previous_item_id' => $array[$i]['previous_item_id'],
5893
                        'next_item_id' => $array[$i]['next_item_id'],
5894
                        'min_score' => $array[$i]['min_score'],
5895
                        'max_score' => $array[$i]['max_score'],
5896
                        'mastery_score' => $array[$i]['mastery_score'],
5897
                        'display_order' => $array[$i]['display_order'],
5898
                        'prerequisite' => $preq,
5899
                        'depth' => $depth,
5900
                        'audio' => $audio,
5901
                        'prerequisite_min_score' => $prerequisiteMinScore,
5902
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5903
                    ];
5904
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5905
                }
5906
            }
5907
        }
5908
    }
5909
5910
    /**
5911
     * Sorts a multi dimensional array by parent id and display order.
5912
     *
5913
     * @author Kevin Van Den Haute
5914
     *
5915
     * @param array $array (array with al the learning path items in it)
5916
     *
5917
     * @return array
5918
     */
5919
    public function sort_tree_array($array)
5920
    {
5921
        foreach ($array as $key => $row) {
5922
            $parent[$key] = $row['parent_item_id'];
5923
            $position[$key] = $row['display_order'];
5924
        }
5925
5926
        if (count($array) > 0) {
5927
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5928
        }
5929
5930
        return $array;
5931
    }
5932
5933
    /**
5934
     * Function that creates a html list of learning path items so that we can add audio files to them.
5935
     *
5936
     * @author Kevin Van Den Haute
5937
     *
5938
     * @return string
5939
     */
5940
    public function overview()
5941
    {
5942
        $return = '';
5943
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5944
5945
        // we need to start a form when we want to update all the mp3 files
5946
        if ('true' == $update_audio) {
5947
            $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">';
5948
        }
5949
        $return .= '<div id="message"></div>';
5950
        if (0 == count($this->items)) {
5951
            $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');
5952
        } else {
5953
            $return_audio = '<table class="data_table">';
5954
            $return_audio .= '<tr>';
5955
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5956
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5957
            $return_audio .= '</tr>';
5958
5959
            if ('true' != $update_audio) {
5960
                $return .= '<div class="col-md-12">';
5961
                $return .= self::return_new_tree($update_audio);
5962
                $return .= '</div>';
5963
                $return .= Display::div(
5964
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5965
                    ['style' => 'float:left; margin-top:15px;width:100%']
5966
                );
5967
            } else {
5968
                $return_audio .= self::return_new_tree($update_audio);
5969
                $return .= $return_audio.'</table>';
5970
            }
5971
5972
            // We need to close the form when we are updating the mp3 files.
5973
            if ('true' == $update_audio) {
5974
                $return .= '<div class="footer-audio">';
5975
                $return .= Display::button(
5976
                    'save_audio',
5977
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5978
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5979
                );
5980
                $return .= '</div>';
5981
            }
5982
        }
5983
5984
        // We need to close the form when we are updating the mp3 files.
5985
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5986
            $return .= '</form>';
5987
        }
5988
5989
        return $return;
5990
    }
5991
5992
    /**
5993
     * @param string $update_audio
5994
     *
5995
     * @return array
5996
     */
5997
    public function processBuildMenuElements($update_audio = 'false')
5998
    {
5999
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6000
        $arrLP = $this->getItemsForForm();
6001
6002
        $this->tree_array($arrLP);
6003
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6004
        unset($this->arrMenu);
6005
        $default_data = null;
6006
        $default_content = null;
6007
        $elements = [];
6008
        $return_audio = null;
6009
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
6010
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6011
        $countItems = count($arrLP);
6012
6013
        $upIcon = Display::return_icon(
6014
            'up.png',
6015
            get_lang('Up'),
6016
            [],
6017
            ICON_SIZE_TINY
6018
        );
6019
6020
        $disableUpIcon = Display::return_icon(
6021
            'up_na.png',
6022
            get_lang('Up'),
6023
            [],
6024
            ICON_SIZE_TINY
6025
        );
6026
6027
        $downIcon = Display::return_icon(
6028
            'down.png',
6029
            get_lang('Down'),
6030
            [],
6031
            ICON_SIZE_TINY
6032
        );
6033
6034
        $disableDownIcon = Display::return_icon(
6035
            'down_na.png',
6036
            get_lang('Down'),
6037
            [],
6038
            ICON_SIZE_TINY
6039
        );
6040
6041
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6042
6043
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
6044
        $plugin = null;
6045
        if ($pluginCalendar) {
6046
            $plugin = LearningCalendarPlugin::create();
6047
        }
6048
6049
        for ($i = 0; $i < $countItems; $i++) {
6050
            $parent_id = $arrLP[$i]['parent_item_id'];
6051
            $title = $arrLP[$i]['title'];
6052
            $title_cut = $arrLP[$i]['title_raw'];
6053
            if (false === $show) {
6054
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6055
            }
6056
            // Link for the documents
6057
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
6058
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6059
                $title_cut = Display::url(
6060
                    $title_cut,
6061
                    $url,
6062
                    [
6063
                        'class' => 'ajax moved',
6064
                        'data-title' => $title,
6065
                        'title' => $title,
6066
                    ]
6067
                );
6068
            }
6069
6070
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6071
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6072
                Session::write('pathItem', $arrLP[$i]['path']);
6073
            }
6074
6075
            $oddClass = 'row_even';
6076
            if (0 == ($i % 2)) {
6077
                $oddClass = 'row_odd';
6078
            }
6079
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6080
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6081
6082
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6083
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6084
            } else {
6085
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6086
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6087
                } else {
6088
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6089
                        $icon = Display::return_icon('certificate.png');
6090
                    } else {
6091
                        $icon = Display::return_icon('folder_document.png');
6092
                    }
6093
                }
6094
            }
6095
6096
            // The audio column.
6097
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6098
            $audio = '';
6099
            if (!$update_audio || 'true' != $update_audio) {
6100
                if (empty($arrLP[$i]['audio'])) {
6101
                    $audio .= '';
6102
                }
6103
            } else {
6104
                $types = self::getChapterTypes();
6105
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6106
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6107
                    if (!empty($arrLP[$i]['audio'])) {
6108
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6109
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6110
                    }
6111
                }
6112
            }
6113
6114
            $return_audio .= Display::span($icon.' '.$title).
6115
                Display::tag(
6116
                    'td',
6117
                    $audio,
6118
                    ['style' => '']
6119
                );
6120
            $return_audio .= '</td>';
6121
            $move_icon = '';
6122
            $move_item_icon = '';
6123
            $edit_icon = '';
6124
            $delete_icon = '';
6125
            $audio_icon = '';
6126
            $prerequisities_icon = '';
6127
            $forumIcon = '';
6128
            $previewIcon = '';
6129
            $pluginCalendarIcon = '';
6130
            $orderIcons = '';
6131
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6132
6133
            if ($is_allowed_to_edit) {
6134
                if (!$update_audio || 'true' != $update_audio) {
6135
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6136
                        $move_icon .= '<a class="moved" href="#">';
6137
                        $move_icon .= Display::return_icon(
6138
                            'move_everywhere.png',
6139
                            get_lang('Move'),
6140
                            [],
6141
                            ICON_SIZE_TINY
6142
                        );
6143
                        $move_icon .= '</a>';
6144
                    }
6145
                }
6146
6147
                // No edit for this item types
6148
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6149
                    if ('dir' != $arrLP[$i]['item_type']) {
6150
                        $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">';
6151
                        $edit_icon .= Display::return_icon(
6152
                            'edit.png',
6153
                            get_lang('Edit section description/name'),
6154
                            [],
6155
                            ICON_SIZE_TINY
6156
                        );
6157
                        $edit_icon .= '</a>';
6158
6159
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6160
                            $forumThread = null;
6161
                            if (isset($this->items[$arrLP[$i]['id']])) {
6162
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6163
                                    $this->course_int_id,
6164
                                    $this->lp_session_id
6165
                                );
6166
                            }
6167
                            if ($forumThread) {
6168
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6169
                                        'action' => 'dissociate_forum',
6170
                                        'id' => $arrLP[$i]['id'],
6171
                                        'lp_id' => $this->lp_id,
6172
                                    ]);
6173
                                $forumIcon = Display::url(
6174
                                    Display::return_icon(
6175
                                        'forum.png',
6176
                                        get_lang('Dissociate the forum of this learning path item'),
6177
                                        [],
6178
                                        ICON_SIZE_TINY
6179
                                    ),
6180
                                    $forumIconUrl,
6181
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6182
                                );
6183
                            } else {
6184
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6185
                                        'action' => 'create_forum',
6186
                                        'id' => $arrLP[$i]['id'],
6187
                                        'lp_id' => $this->lp_id,
6188
                                    ]);
6189
                                $forumIcon = Display::url(
6190
                                    Display::return_icon(
6191
                                        'forum.png',
6192
                                        get_lang('Associate a forum to this learning path item'),
6193
                                        [],
6194
                                        ICON_SIZE_TINY
6195
                                    ),
6196
                                    $forumIconUrl,
6197
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6198
                                );
6199
                            }
6200
                        }
6201
                    } else {
6202
                        $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">';
6203
                        $edit_icon .= Display::return_icon(
6204
                            'edit.png',
6205
                            get_lang('Edit section description/name'),
6206
                            [],
6207
                            ICON_SIZE_TINY
6208
                        );
6209
                        $edit_icon .= '</a>';
6210
                    }
6211
                } else {
6212
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6213
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6214
                        $edit_icon .= Display::return_icon(
6215
                            'edit.png',
6216
                            get_lang('Edit'),
6217
                            [],
6218
                            ICON_SIZE_TINY
6219
                        );
6220
                        $edit_icon .= '</a>';
6221
                    }
6222
                }
6223
6224
                if ($pluginCalendar) {
6225
                    $pluginLink = $pluginUrl.
6226
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6227
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6228
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6229
                    if ($itemInfo && 1 == $itemInfo['value']) {
6230
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6231
                    }
6232
                    $pluginCalendarIcon = Display::url(
6233
                        $iconCalendar,
6234
                        $pluginLink,
6235
                        ['class' => 'btn btn-default']
6236
                    );
6237
                }
6238
6239
                if ('final_item' != $arrLP[$i]['item_type']) {
6240
                    $orderIcons = Display::url(
6241
                        $upIcon,
6242
                        'javascript:void(0)',
6243
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6244
                    );
6245
                    $orderIcons .= Display::url(
6246
                        $downIcon,
6247
                        'javascript:void(0)',
6248
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6249
                    );
6250
                }
6251
6252
                $delete_icon .= ' <a
6253
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6254
                    onclick="return confirmation(\''.addslashes($title).'\');"
6255
                    class="btn btn-default">';
6256
                $delete_icon .= Display::return_icon(
6257
                    'delete.png',
6258
                    get_lang('Delete section'),
6259
                    [],
6260
                    ICON_SIZE_TINY
6261
                );
6262
                $delete_icon .= '</a>';
6263
6264
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6265
                $previewImage = Display::return_icon(
6266
                    'preview_view.png',
6267
                    get_lang('Preview'),
6268
                    [],
6269
                    ICON_SIZE_TINY
6270
                );
6271
6272
                switch ($arrLP[$i]['item_type']) {
6273
                    case TOOL_DOCUMENT:
6274
                    case TOOL_LP_FINAL_ITEM:
6275
                    case TOOL_READOUT_TEXT:
6276
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6277
                        $previewIcon = Display::url(
6278
                            $previewImage,
6279
                            $urlPreviewLink,
6280
                            [
6281
                                'target' => '_blank',
6282
                                'class' => 'btn btn-default',
6283
                                'data-title' => $arrLP[$i]['title'],
6284
                                'title' => $arrLP[$i]['title'],
6285
                            ]
6286
                        );
6287
                        break;
6288
                    case TOOL_THREAD:
6289
                    case TOOL_FORUM:
6290
                    case TOOL_QUIZ:
6291
                    case TOOL_STUDENTPUBLICATION:
6292
                    case TOOL_LP_FINAL_ITEM:
6293
                    case TOOL_LINK:
6294
                        $class = 'btn btn-default';
6295
                        $target = '_blank';
6296
                        $link = self::rl_get_resource_link_for_learnpath(
6297
                            $this->course_int_id,
6298
                            $this->lp_id,
6299
                            $arrLP[$i]['id'],
6300
                            0
6301
                        );
6302
                        $previewIcon = Display::url(
6303
                            $previewImage,
6304
                            $link,
6305
                            [
6306
                                'class' => $class,
6307
                                'data-title' => $arrLP[$i]['title'],
6308
                                'title' => $arrLP[$i]['title'],
6309
                                'target' => $target,
6310
                            ]
6311
                        );
6312
                        break;
6313
                    default:
6314
                        $previewIcon = Display::url(
6315
                            $previewImage,
6316
                            $url.'&action=view_item',
6317
                            ['class' => 'btn btn-default', 'target' => '_blank']
6318
                        );
6319
                        break;
6320
                }
6321
6322
                if ('dir' != $arrLP[$i]['item_type']) {
6323
                    $prerequisities_icon = Display::url(
6324
                        Display::return_icon(
6325
                            'accept.png',
6326
                            get_lang('Prerequisites'),
6327
                            [],
6328
                            ICON_SIZE_TINY
6329
                        ),
6330
                        $url.'&action=edit_item_prereq',
6331
                        ['class' => 'btn btn-default']
6332
                    );
6333
                    if ('final_item' != $arrLP[$i]['item_type']) {
6334
                        /*$move_item_icon = Display::url(
6335
                            Display::return_icon(
6336
                                'move.png',
6337
                                get_lang('Move'),
6338
                                [],
6339
                                ICON_SIZE_TINY
6340
                            ),
6341
                            $url.'&action=move_item',
6342
                            ['class' => 'btn btn-default']
6343
                        );*/
6344
                    }
6345
                    $audio_icon = Display::url(
6346
                        Display::return_icon(
6347
                            'audio.png',
6348
                            get_lang('Upload'),
6349
                            [],
6350
                            ICON_SIZE_TINY
6351
                        ),
6352
                        $url.'&action=add_audio',
6353
                        ['class' => 'btn btn-default']
6354
                    );
6355
                }
6356
            }
6357
            if ('true' != $update_audio) {
6358
                $row = $move_icon.' '.$icon.
6359
                    Display::span($title_cut).
6360
                    Display::tag(
6361
                        'div',
6362
                        "<div class=\"btn-group btn-group-xs\">
6363
                                    $previewIcon
6364
                                    $audio
6365
                                    $edit_icon
6366
                                    $pluginCalendarIcon
6367
                                    $forumIcon
6368
                                    $prerequisities_icon
6369
                                    $move_item_icon
6370
                                    $audio_icon
6371
                                    $orderIcons
6372
                                    $delete_icon
6373
                                </div>",
6374
                        ['class' => 'btn-toolbar button_actions']
6375
                    );
6376
            } else {
6377
                $row =
6378
                    Display::span($title.$icon).
6379
                    Display::span($audio, ['class' => 'button_actions']);
6380
            }
6381
6382
            $default_data[$arrLP[$i]['id']] = $row;
6383
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6384
6385
            if (empty($parent_id)) {
6386
                $elements[$arrLP[$i]['id']]['data'] = $row;
6387
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6388
            } else {
6389
                $parent_arrays = [];
6390
                if ($arrLP[$i]['depth'] > 1) {
6391
                    // Getting list of parents
6392
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6393
                        foreach ($arrLP as $item) {
6394
                            if ($item['id'] == $parent_id) {
6395
                                if (0 == $item['parent_item_id']) {
6396
                                    $parent_id = $item['id'];
6397
                                    break;
6398
                                } else {
6399
                                    $parent_id = $item['parent_item_id'];
6400
                                    if (empty($parent_arrays)) {
6401
                                        $parent_arrays[] = intval($item['id']);
6402
                                    }
6403
                                    $parent_arrays[] = $parent_id;
6404
                                    break;
6405
                                }
6406
                            }
6407
                        }
6408
                    }
6409
                }
6410
6411
                if (!empty($parent_arrays)) {
6412
                    $parent_arrays = array_reverse($parent_arrays);
6413
                    $val = '$elements';
6414
                    $x = 0;
6415
                    foreach ($parent_arrays as $item) {
6416
                        if ($x != count($parent_arrays) - 1) {
6417
                            $val .= '["'.$item.'"]["children"]';
6418
                        } else {
6419
                            $val .= '["'.$item.'"]["children"]';
6420
                        }
6421
                        $x++;
6422
                    }
6423
                    $val .= "";
6424
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6425
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6426
                } else {
6427
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6428
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6429
                }
6430
            }
6431
        }
6432
6433
        return [
6434
            'elements' => $elements,
6435
            'default_data' => $default_data,
6436
            'default_content' => $default_content,
6437
            'return_audio' => $return_audio,
6438
        ];
6439
    }
6440
6441
    /**
6442
     * @param string $updateAudio true/false strings
6443
     *
6444
     * @return string
6445
     */
6446
    public function returnLpItemList($updateAudio)
6447
    {
6448
        $result = $this->processBuildMenuElements($updateAudio);
6449
6450
        $html = self::print_recursive(
6451
            $result['elements'],
6452
            $result['default_data'],
6453
            $result['default_content']
6454
        );
6455
6456
        if (!empty($html)) {
6457
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6458
        }
6459
6460
        return $html;
6461
    }
6462
6463
    /**
6464
     * @param string $update_audio
6465
     * @param bool   $drop_element_here
6466
     *
6467
     * @return string
6468
     */
6469
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6470
    {
6471
        $result = $this->processBuildMenuElements($update_audio);
6472
6473
        $list = '<ul id="lp_item_list">';
6474
        $tree = $this->print_recursive(
6475
            $result['elements'],
6476
            $result['default_data'],
6477
            $result['default_content']
6478
        );
6479
6480
        if (!empty($tree)) {
6481
            $list .= $tree;
6482
        } else {
6483
            if ($drop_element_here) {
6484
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6485
            }
6486
        }
6487
        $list .= '</ul>';
6488
6489
        $return = Display::panelCollapse(
6490
            $this->name,
6491
            $list,
6492
            'scorm-list',
6493
            null,
6494
            'scorm-list-accordion',
6495
            'scorm-list-collapse'
6496
        );
6497
6498
        if ('true' === $update_audio) {
6499
            $return = $result['return_audio'];
6500
        }
6501
6502
        return $return;
6503
    }
6504
6505
    /**
6506
     * @param array $elements
6507
     * @param array $default_data
6508
     * @param array $default_content
6509
     *
6510
     * @return string
6511
     */
6512
    public function print_recursive($elements, $default_data, $default_content)
6513
    {
6514
        $return = '';
6515
        foreach ($elements as $key => $item) {
6516
            if (isset($item['load_data']) || empty($item['data'])) {
6517
                $item['data'] = $default_data[$item['load_data']];
6518
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6519
            }
6520
            $sub_list = '';
6521
            if (isset($item['type']) && 'dir' === $item['type']) {
6522
                // empty value
6523
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6524
            }
6525
            if (empty($item['children'])) {
6526
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6527
                $active = null;
6528
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6529
                    $active = 'active';
6530
                }
6531
                $return .= Display::tag(
6532
                    'li',
6533
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6534
                    ['id' => $key, 'class' => 'record li_container']
6535
                );
6536
            } else {
6537
                // Sections
6538
                $data = '';
6539
                if (isset($item['children'])) {
6540
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6541
                }
6542
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6543
                $return .= Display::tag(
6544
                    'li',
6545
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6546
                    ['id' => $key, 'class' => 'record li_container']
6547
                );
6548
            }
6549
        }
6550
6551
        return $return;
6552
    }
6553
6554
    /**
6555
     * This function builds the action menu.
6556
     *
6557
     * @param bool   $returnString           Optional
6558
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6559
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6560
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6561
     * @param string $action
6562
     *
6563
     * @return string
6564
     */
6565
    public function build_action_menu(
6566
        $returnString = false,
6567
        $showRequirementButtons = true,
6568
        $isConfigPage = false,
6569
        $allowExpand = true,
6570
        $action = ''
6571
    ) {
6572
        $actionsRight = '';
6573
        $lpId = $this->lp_id;
6574
        $back = Display::url(
6575
            Display::return_icon(
6576
                'back.png',
6577
                get_lang('Back to learning paths'),
6578
                '',
6579
                ICON_SIZE_MEDIUM
6580
            ),
6581
            'lp_controller.php?'.api_get_cidreq()
6582
        );
6583
6584
        /*if ($backToBuild) {
6585
            $back = Display::url(
6586
                Display::return_icon(
6587
                    'back.png',
6588
                    get_lang('GoBack'),
6589
                    '',
6590
                    ICON_SIZE_MEDIUM
6591
                ),
6592
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6593
            );
6594
        }*/
6595
6596
        $actionsLeft = $back;
6597
6598
        $actionsLeft .= Display::url(
6599
            Display::return_icon(
6600
                'preview_view.png',
6601
                get_lang('Preview'),
6602
                '',
6603
                ICON_SIZE_MEDIUM
6604
            ),
6605
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6606
                'action' => 'view',
6607
                'lp_id' => $lpId,
6608
                'isStudentView' => 'true',
6609
            ])
6610
        );
6611
6612
        $actionsLeft .= Display::url(
6613
            Display::return_icon(
6614
                'upload_audio.png',
6615
                get_lang('Add audio'),
6616
                '',
6617
                ICON_SIZE_MEDIUM
6618
            ),
6619
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6620
                'action' => 'admin_view',
6621
                'lp_id' => $lpId,
6622
                'updateaudio' => 'true',
6623
            ])
6624
        );
6625
6626
        $subscriptionSettings = self::getSubscriptionSettings();
6627
6628
        $request = api_request_uri();
6629
        if (false === strpos($request, 'edit')) {
6630
            $actionsLeft .= Display::url(
6631
                Display::return_icon(
6632
                    'settings.png',
6633
                    get_lang('Course settings'),
6634
                    '',
6635
                    ICON_SIZE_MEDIUM
6636
                ),
6637
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6638
                    'action' => 'edit',
6639
                    'lp_id' => $lpId,
6640
                ])
6641
            );
6642
        }
6643
6644
        if ((strpos($request, 'build') === false &&
6645
            strpos($request, 'add_item') === false) ||
6646
            in_array($action, ['add_audio'])
6647
        ) {
6648
            $actionsLeft .= Display::url(
6649
                Display::return_icon(
6650
                    'edit.png',
6651
                    get_lang('Edit'),
6652
                    '',
6653
                    ICON_SIZE_MEDIUM
6654
                ),
6655
                'lp_controller.php?'.http_build_query([
6656
                    'action' => 'build',
6657
                    'lp_id' => $lpId,
6658
                ]).'&'.api_get_cidreq()
6659
            );
6660
        }
6661
6662
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6663
            if (1 == $this->subscribeUsers &&
6664
                $subscriptionSettings['allow_add_users_to_lp']) {
6665
                $actionsLeft .= Display::url(
6666
                    Display::return_icon(
6667
                        'user.png',
6668
                        get_lang('Subscribe users to learning path'),
6669
                        '',
6670
                        ICON_SIZE_MEDIUM
6671
                    ),
6672
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6673
                );
6674
            }
6675
        }
6676
6677
        if ($allowExpand) {
6678
            $actionsLeft .= Display::url(
6679
                Display::return_icon(
6680
                    'expand.png',
6681
                    get_lang('Expand'),
6682
                    ['id' => 'expand'],
6683
                    ICON_SIZE_MEDIUM
6684
                ).
6685
                Display::return_icon(
6686
                    'contract.png',
6687
                    get_lang('Collapse'),
6688
                    ['id' => 'contract', 'class' => 'hide'],
6689
                    ICON_SIZE_MEDIUM
6690
                ),
6691
                '#',
6692
                ['role' => 'button', 'id' => 'hide_bar_template']
6693
            );
6694
        }
6695
6696
        if ($showRequirementButtons) {
6697
            $buttons = [
6698
                [
6699
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6700
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6701
                        'action' => 'set_previous_step_as_prerequisite',
6702
                        'lp_id' => $lpId,
6703
                    ]),
6704
                ],
6705
                [
6706
                    'title' => get_lang('Clear all prerequisites'),
6707
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6708
                        'action' => 'clear_prerequisites',
6709
                        'lp_id' => $lpId,
6710
                    ]),
6711
                ],
6712
            ];
6713
            $actionsRight = Display::groupButtonWithDropDown(
6714
                get_lang('Prerequisites options'),
6715
                $buttons,
6716
                true
6717
            );
6718
        }
6719
6720
        $toolbar = Display::toolbarAction(
6721
            'actions-lp-controller',
6722
            [$actionsLeft, $actionsRight]
6723
        );
6724
6725
        if ($returnString) {
6726
            return $toolbar;
6727
        }
6728
6729
        echo $toolbar;
6730
    }
6731
6732
    /**
6733
     * Creates the default learning path folder.
6734
     *
6735
     * @param array $course
6736
     * @param int   $creatorId
6737
     *
6738
     * @return bool
6739
     */
6740
    public static function generate_learning_path_folder($course, $creatorId = 0)
6741
    {
6742
        // Creating learning_path folder
6743
        $dir = 'learning_path';
6744
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6745
        $folder = false;
6746
        $folderData = create_unexisting_directory(
6747
            $course,
6748
            $creatorId,
6749
            0,
6750
            null,
6751
            0,
6752
            '',
6753
            $dir,
6754
            get_lang('Learning paths'),
6755
            0
6756
        );
6757
6758
        if (!empty($folderData)) {
6759
            $folder = true;
6760
        }
6761
6762
        return $folder;
6763
    }
6764
6765
    /**
6766
     * @param array  $course
6767
     * @param string $lp_name
6768
     * @param int    $creatorId
6769
     *
6770
     * @return array
6771
     */
6772
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6773
    {
6774
        $filepath = '';
6775
        $dir = '/learning_path/';
6776
6777
        if (empty($lp_name)) {
6778
            $lp_name = $this->name;
6779
        }
6780
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6781
        $folder = self::generate_learning_path_folder($course, $creatorId);
6782
6783
        // Limits title size
6784
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6785
        $dir = $dir.$title;
6786
6787
        // Creating LP folder
6788
        $documentId = null;
6789
        if ($folder) {
6790
            $folderData = create_unexisting_directory(
6791
                $course,
6792
                $creatorId,
6793
                0,
6794
                0,
6795
                0,
6796
                $filepath,
6797
                $dir,
6798
                $lp_name
6799
            );
6800
            if (!empty($folderData)) {
6801
                $folder = true;
6802
            }
6803
6804
            $documentId = $folderData->getIid();
6805
            $dir = $dir.'/';
6806
            if ($folder) {
6807
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6808
            }
6809
        }
6810
6811
        if (empty($documentId)) {
6812
            $dir = api_remove_trailing_slash($dir);
6813
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6814
        }
6815
6816
        $array = [
6817
            'dir' => $dir,
6818
            'filepath' => $filepath,
6819
            'folder' => $folder,
6820
            'id' => $documentId,
6821
        ];
6822
6823
        return $array;
6824
    }
6825
6826
    /**
6827
     * Create a new document //still needs some finetuning.
6828
     *
6829
     * @param array  $courseInfo
6830
     * @param string $content
6831
     * @param string $title
6832
     * @param string $extension
6833
     * @param int    $parentId
6834
     * @param int    $creatorId  creator id
6835
     *
6836
     * @return int
6837
     */
6838
    public function create_document(
6839
        $courseInfo,
6840
        $content = '',
6841
        $title = '',
6842
        $extension = 'html',
6843
        $parentId = 0,
6844
        $creatorId = 0
6845
    ) {
6846
        if (!empty($courseInfo)) {
6847
            $course_id = $courseInfo['real_id'];
6848
        } else {
6849
            $course_id = api_get_course_int_id();
6850
        }
6851
6852
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6853
        $sessionId = api_get_session_id();
6854
6855
        // Generates folder
6856
        $result = $this->generate_lp_folder($courseInfo);
6857
        $dir = $result['dir'];
6858
6859
        if (empty($parentId) || '/' == $parentId) {
6860
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6861
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6862
6863
            if ('/' === $parentId) {
6864
                $dir = '/';
6865
            }
6866
6867
            // Please, do not modify this dirname formatting.
6868
            if (strstr($dir, '..')) {
6869
                $dir = '/';
6870
            }
6871
6872
            if (!empty($dir[0]) && '.' == $dir[0]) {
6873
                $dir = substr($dir, 1);
6874
            }
6875
            if (!empty($dir[0]) && '/' != $dir[0]) {
6876
                $dir = '/'.$dir;
6877
            }
6878
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6879
                $dir .= '/';
6880
            }
6881
        } else {
6882
            $parentInfo = DocumentManager::get_document_data_by_id(
6883
                $parentId,
6884
                $courseInfo['code']
6885
            );
6886
            if (!empty($parentInfo)) {
6887
                $dir = $parentInfo['path'].'/';
6888
            }
6889
        }
6890
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6891
        // is already escaped twice when it gets here.
6892
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6893
        if (!empty($title)) {
6894
            $title = api_replace_dangerous_char(stripslashes($title));
6895
        } else {
6896
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6897
        }
6898
6899
        $title = disable_dangerous_file($title);
6900
        $filename = $title;
6901
        $content = !empty($content) ? $content : $_POST['content_lp'];
6902
        $tmp_filename = $filename;
6903
        $filename = $tmp_filename.'.'.$extension;
6904
6905
        if ('html' === $extension) {
6906
            $content = stripslashes($content);
6907
            $content = str_replace(
6908
                api_get_path(WEB_COURSE_PATH),
6909
                api_get_path(REL_PATH).'courses/',
6910
                $content
6911
            );
6912
6913
            // Change the path of mp3 to absolute.
6914
            // The first regexp deals with :// urls.
6915
            $content = preg_replace(
6916
                "|(flashvars=\"file=)([^:/]+)/|",
6917
                "$1".api_get_path(
6918
                    REL_COURSE_PATH
6919
                ).$courseInfo['path'].'/document/',
6920
                $content
6921
            );
6922
            // The second regexp deals with audio/ urls.
6923
            $content = preg_replace(
6924
                "|(flashvars=\"file=)([^/]+)/|",
6925
                "$1".api_get_path(
6926
                    REL_COURSE_PATH
6927
                ).$courseInfo['path'].'/document/$2/',
6928
                $content
6929
            );
6930
            // For flv player: To prevent edition problem with firefox,
6931
            // we have to use a strange tip (don't blame me please).
6932
            $content = str_replace(
6933
                '</body>',
6934
                '<style type="text/css">body{}</style></body>',
6935
                $content
6936
            );
6937
        }
6938
6939
        $save_file_path = $dir.$filename;
6940
6941
        $document = DocumentManager::addDocument(
6942
            $courseInfo,
6943
            $save_file_path,
6944
            'file',
6945
            '',
6946
            $tmp_filename,
6947
            '',
6948
            0, //readonly
6949
            true,
6950
            null,
6951
            $sessionId,
6952
            $creatorId,
6953
            false,
6954
            $content,
6955
            $parentId
6956
        );
6957
6958
        $document_id = $document->getIid();
6959
        if ($document_id) {
6960
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6961
            $new_title = $originalTitle;
6962
6963
            if ($new_comment || $new_title) {
6964
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6965
                $ct = '';
6966
                if ($new_comment) {
6967
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6968
                }
6969
                if ($new_title) {
6970
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6971
                }
6972
6973
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6974
                        WHERE c_id = $course_id AND id = $document_id ";
6975
                Database::query($sql);
6976
            }
6977
        }
6978
6979
        return $document_id;
6980
    }
6981
6982
    /**
6983
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6984
     */
6985
    public function edit_document()
6986
    {
6987
        $repo = Container::getDocumentRepository();
6988
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6989
            $id = (int) $_REQUEST['document_id'];
6990
            /** @var CDocument $document */
6991
            $document = $repo->find($id);
6992
6993
            if ($document->getResourceNode()->hasEditableContent()) {
6994
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6995
            }
6996
6997
            $document->setTitle($_REQUEST['title']);
6998
            $repo->getEntityManager()->persist($document);
6999
            $repo->getEntityManager()->flush();
7000
        }
7001
    }
7002
7003
    /**
7004
     * Displays the selected item, with a panel for manipulating the item.
7005
     *
7006
     * @param CLpItem $lpItem
7007
     * @param string  $msg
7008
     * @param bool    $show_actions
7009
     *
7010
     * @return string
7011
     */
7012
    public function display_item($lpItem, $msg = null, $show_actions = true)
7013
    {
7014
        $course_id = api_get_course_int_id();
7015
        $return = '';
7016
7017
        if (empty($lpItem)) {
7018
            return '';
7019
        }
7020
        $item_id = $lpItem->getIid();
7021
        $itemType = $lpItem->getItemType();
7022
        $lpId = $lpItem->getLpId();
7023
        $path = $lpItem->getPath();
7024
7025
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
7026
7027
        // Prevents wrong parent selection for document, see Bug#1251.
7028
        if ('dir' !== $itemType) {
7029
            Session::write('parent_item_id', $lpItem->getParentItemId());
7030
        }
7031
7032
        if ($show_actions) {
7033
            $return .= $this->displayItemMenu($lpItem);
7034
        }
7035
        $return .= '<div style="padding:10px;">';
7036
7037
        if ('' != $msg) {
7038
            $return .= $msg;
7039
        }
7040
7041
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
7042
7043
        switch ($itemType) {
7044
            case TOOL_THREAD:
7045
                $link = $this->rl_get_resource_link_for_learnpath(
7046
                    $course_id,
7047
                    $lpId,
7048
                    $item_id,
7049
                    0
7050
                );
7051
                $return .= Display::url(
7052
                    get_lang('Go to thread'),
7053
                    $link,
7054
                    ['class' => 'btn btn-primary']
7055
                );
7056
                break;
7057
            case TOOL_FORUM:
7058
                $return .= Display::url(
7059
                    get_lang('Go to the forum'),
7060
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
7061
                    ['class' => 'btn btn-primary']
7062
                );
7063
                break;
7064
            case TOOL_QUIZ:
7065
                if (!empty($path)) {
7066
                    $exercise = new Exercise();
7067
                    $exercise->read($path);
7068
                    $return .= $exercise->description.'<br />';
7069
                    $return .= Display::url(
7070
                        get_lang('Go to exercise'),
7071
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7072
                        ['class' => 'btn btn-primary']
7073
                    );
7074
                }
7075
                break;
7076
            case TOOL_LP_FINAL_ITEM:
7077
                $return .= $this->getSavedFinalItem();
7078
                break;
7079
            case TOOL_DOCUMENT:
7080
            case TOOL_READOUT_TEXT:
7081
                $repo = Container::getDocumentRepository();
7082
                /** @var CDocument $document */
7083
                $document = $repo->find($lpItem->getPath());
7084
                $return .= $this->display_document($document, true, true);
7085
                break;
7086
            case TOOL_HOTPOTATOES:
7087
                $return .= $this->display_document($document, false, true);
7088
                break;
7089
        }
7090
        $return .= '</div>';
7091
7092
        return $return;
7093
    }
7094
7095
    /**
7096
     * Shows the needed forms for editing a specific item.
7097
     *
7098
     * @param CLpItem $lpItem
7099
     *
7100
     * @throws Exception
7101
     * @throws HTML_QuickForm_Error
7102
     *
7103
     * @return string
7104
     */
7105
    public function display_edit_item($lpItem)
7106
    {
7107
        $course_id = api_get_course_int_id();
7108
        $return = '';
7109
7110
        if (empty($lpItem)) {
7111
            return '';
7112
        }
7113
        $item_id = $lpItem->getIid();
7114
        $itemType = $lpItem->getItemType();
7115
        $path = $lpItem->getPath();
7116
7117
        switch ($itemType) {
7118
            case 'dir':
7119
            case 'asset':
7120
            case 'sco':
7121
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
7122
                    $return .= $this->displayItemMenu($lpItem);
7123
                    $return .= $this->display_item_form(
7124
                        $lpItem,
7125
                        'edit'
7126
                    );
7127
                } else {
7128
                    $return .= $this->display_item_form(
7129
                        $lpItem,
7130
                        'edit_item'
7131
                    );
7132
                }
7133
                break;
7134
            case TOOL_LP_FINAL_ITEM:
7135
            case TOOL_DOCUMENT:
7136
            case TOOL_READOUT_TEXT:
7137
                $return .= $this->displayItemMenu($lpItem);
7138
                $return .= $this->displayDocumentForm('edit', $lpItem);
7139
                break;
7140
            case TOOL_LINK:
7141
                $link = null;
7142
                if (!empty($path)) {
7143
                    $repo = Container::getLinkRepository();
7144
                    $link = $repo->find($path);
7145
                }
7146
                $return .= $this->displayItemMenu($lpItem);
7147
                $return .= $this->display_link_form('edit', $lpItem, $link);
7148
7149
                break;
7150
            case TOOL_QUIZ:
7151
                if (!empty($path)) {
7152
                    $repo = Container::getExerciseRepository();
7153
                    $resource = $repo->find($path);
7154
                }
7155
                $return .= $this->displayItemMenu($lpItem);
7156
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7157
                break;
7158
            /*case TOOL_HOTPOTATOES:
7159
                $return .= $this->displayItemMenu($lpItem);
7160
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7161
                break;*/
7162
            case TOOL_STUDENTPUBLICATION:
7163
                if (!empty($path)) {
7164
                    $repo = Container::getStudentPublicationRepository();
7165
                    $resource = $repo->find($path);
7166
                }
7167
                $return .= $this->displayItemMenu($lpItem);
7168
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7169
                break;
7170
            case TOOL_FORUM:
7171
                if (!empty($path)) {
7172
                    $repo = Container::getForumRepository();
7173
                    $resource = $repo->find($path);
7174
                }
7175
                $return .= $this->displayItemMenu($lpItem);
7176
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7177
                break;
7178
            case TOOL_THREAD:
7179
                if (!empty($path)) {
7180
                    $repo = Container::getForumPostRepository();
7181
                    $resource = $repo->find($path);
7182
                }
7183
                $return .= $this->displayItemMenu($lpItem);
7184
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7185
                break;
7186
        }
7187
7188
        return $return;
7189
    }
7190
7191
    /**
7192
     * Function that displays a list with al the resources that
7193
     * could be added to the learning path.
7194
     *
7195
     * @throws Exception
7196
     * @throws HTML_QuickForm_Error
7197
     *
7198
     * @return bool
7199
     */
7200
    public function displayResources()
7201
    {
7202
        $course_code = api_get_course_id();
7203
7204
        // Get all the docs.
7205
        $documents = $this->get_documents(true);
7206
7207
        // Get all the exercises.
7208
        $exercises = $this->get_exercises();
7209
7210
        // Get all the links.
7211
        $links = $this->get_links();
7212
7213
        // Get all the student publications.
7214
        $works = $this->get_student_publications();
7215
7216
        // Get all the forums.
7217
        $forums = $this->get_forums();
7218
7219
        // Get the final item form (see BT#11048) .
7220
        $finish = $this->getFinalItemForm();
7221
7222
        $headers = [
7223
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7224
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7225
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7226
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7227
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7228
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7229
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7230
        ];
7231
7232
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7233
        $section = $this->displayNewSectionForm();
7234
7235
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7236
7237
        echo Display::tabs(
7238
            $headers,
7239
            [
7240
                $documents,
7241
                $exercises,
7242
                $links,
7243
                $works,
7244
                $forums,
7245
                $section,
7246
                $finish,
7247
            ],
7248
            'resource_tab',
7249
            [],
7250
            [],
7251
            $selected
7252
        );
7253
7254
        return true;
7255
    }
7256
7257
    /**
7258
     * Returns the extension of a document.
7259
     *
7260
     * @param string $filename
7261
     *
7262
     * @return string Extension (part after the last dot)
7263
     */
7264
    public function get_extension($filename)
7265
    {
7266
        $explode = explode('.', $filename);
7267
7268
        return $explode[count($explode) - 1];
7269
    }
7270
7271
    /**
7272
     * @return string
7273
     */
7274
    public function getCurrentBuildingModeURL()
7275
    {
7276
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7277
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7278
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7279
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7280
7281
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7282
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7283
7284
        return $currentUrl;
7285
    }
7286
7287
    /**
7288
     * Displays a document by id.
7289
     *
7290
     * @param CDocument $document
7291
     * @param bool      $show_title
7292
     * @param bool      $iframe
7293
     * @param bool      $edit_link
7294
     *
7295
     * @return string
7296
     */
7297
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7298
    {
7299
        $return = '';
7300
        if (!$document) {
7301
            return '';
7302
        }
7303
7304
        $repo = Container::getDocumentRepository();
7305
7306
        // TODO: Add a path filter.
7307
        if ($iframe) {
7308
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7309
            $url = $repo->getResourceFileUrl($document);
7310
7311
            $return .= '<iframe
7312
                id="learnpath_preview_frame"
7313
                frameborder="0"
7314
                height="400"
7315
                width="100%"
7316
                scrolling="auto"
7317
                src="'.$url.'"></iframe>';
7318
        } else {
7319
            $return = $repo->getResourceFileContent($document);
7320
        }
7321
7322
        return $return;
7323
    }
7324
7325
    /**
7326
     * Return HTML form to add/edit a link item.
7327
     *
7328
     * @param string  $action (add/edit)
7329
     * @param CLpItem $lpItem
7330
     * @param CLink   $link
7331
     *
7332
     * @throws Exception
7333
     * @throws HTML_QuickForm_Error
7334
     *
7335
     * @return string HTML form
7336
     */
7337
    public function display_link_form($action = 'add', $lpItem, $link)
7338
    {
7339
        $item_url = '';
7340
        if ($link) {
7341
            $item_url = stripslashes($link->getUrl());
7342
        }
7343
        $form = new FormValidator(
7344
            'edit_link',
7345
            'POST',
7346
            $this->getCurrentBuildingModeURL()
7347
        );
7348
7349
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7350
7351
        $urlAttributes = ['class' => 'learnpath_item_form'];
7352
        $urlAttributes['disabled'] = 'disabled';
7353
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7354
        $form->setDefault('url', $item_url);
7355
7356
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7357
7358
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7359
    }
7360
7361
    /**
7362
     * Return HTML form to add/edit a quiz.
7363
     *
7364
     * @param string  $action   Action (add/edit)
7365
     * @param CLpItem $lpItem   Item ID if already exists
7366
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7367
     *
7368
     * @throws Exception
7369
     *
7370
     * @return string HTML form
7371
     */
7372
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7373
    {
7374
        $form = new FormValidator(
7375
            'quiz_form',
7376
            'POST',
7377
            $this->getCurrentBuildingModeURL()
7378
        );
7379
7380
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7381
7382
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7383
7384
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7385
    }
7386
7387
    /**
7388
     * Return the form to display the forum edit/add option.
7389
     *
7390
     * @param CLpItem $lpItem
7391
     *
7392
     * @throws Exception
7393
     *
7394
     * @return string HTML form
7395
     */
7396
    public function display_forum_form($action = 'add', $lpItem, $resource)
7397
    {
7398
        $form = new FormValidator(
7399
            'forum_form',
7400
            'POST',
7401
            $this->getCurrentBuildingModeURL()
7402
        );
7403
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7404
7405
        if ('add' === $action) {
7406
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7407
        } else {
7408
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7409
        }
7410
7411
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7412
    }
7413
7414
    /**
7415
     * Return HTML form to add/edit forum threads.
7416
     *
7417
     * @param string  $action
7418
     * @param CLpItem $lpItem
7419
     * @param string  $resource
7420
     *
7421
     * @throws Exception
7422
     *
7423
     * @return string HTML form
7424
     */
7425
    public function display_thread_form($action = 'add', $lpItem, $resource)
7426
    {
7427
        $form = new FormValidator(
7428
            'thread_form',
7429
            'POST',
7430
            $this->getCurrentBuildingModeURL()
7431
        );
7432
7433
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7434
7435
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7436
7437
        return $form->returnForm();
7438
    }
7439
7440
    /**
7441
     * Return the HTML form to display an item (generally a dir item).
7442
     *
7443
     * @param CLpItem $lpItem
7444
     * @param string  $title
7445
     * @param string  $action
7446
     * @param string  $extra_info
7447
     *
7448
     * @throws Exception
7449
     * @throws HTML_QuickForm_Error
7450
     *
7451
     * @return string HTML form
7452
     */
7453
    public function display_item_form(
7454
        $lpItem,
7455
        $action = 'add_item'
7456
    ) {
7457
        $item_type = $lpItem->getItemType();
7458
7459
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7460
7461
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7462
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7463
7464
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7465
7466
        return $form->returnForm();
7467
    }
7468
7469
    /**
7470
     * Return HTML form to add/edit a student publication (work).
7471
     *
7472
     * @param string              $action
7473
     * @param CStudentPublication $resource
7474
     *
7475
     * @throws Exception
7476
     *
7477
     * @return string HTML form
7478
     */
7479
    public function display_student_publication_form(
7480
        $action = 'add',
7481
        CLpItem $lpItem,
7482
        $resource
7483
    ) {
7484
        $form = new FormValidator('frm_student_publication', 'post', '#');
7485
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7486
7487
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7488
7489
        $return = '<div class="sectioncomment">';
7490
        $return .= $form->returnForm();
7491
        $return .= '</div>';
7492
7493
        return $return;
7494
    }
7495
7496
    public function displayNewSectionForm()
7497
    {
7498
        $action = 'add_item';
7499
        $item_type = 'dir';
7500
7501
        $lpItem = new CLpItem();
7502
        $lpItem->setItemType('dir');
7503
7504
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7505
7506
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7507
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7508
7509
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7510
        $form->addElement('hidden', 'type', 'dir');
7511
7512
        return $form->returnForm();
7513
    }
7514
7515
    /**
7516
     * Returns the form to update or create a document.
7517
     *
7518
     * @param string  $action (add/edit)
7519
     * @param CLpItem $lpItem
7520
     *
7521
     * @throws HTML_QuickForm_Error
7522
     * @throws Exception
7523
     *
7524
     * @return string HTML form
7525
     */
7526
    public function displayDocumentForm($action = 'add', $lpItem = null)
7527
    {
7528
        if (empty($lpItem)) {
7529
            return '';
7530
        }
7531
7532
        $courseInfo = api_get_course_info();
7533
7534
        $form = new FormValidator(
7535
            'form',
7536
            'POST',
7537
            $this->getCurrentBuildingModeURL(),
7538
            '',
7539
            ['enctype' => 'multipart/form-data']
7540
        );
7541
7542
        $data = $this->generate_lp_folder($courseInfo);
7543
7544
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7545
7546
        switch ($action) {
7547
            case 'add':
7548
                $folders = DocumentManager::get_all_document_folders(
7549
                    $courseInfo,
7550
                    0,
7551
                    true
7552
                );
7553
                DocumentManager::build_directory_selector(
7554
                    $folders,
7555
                    '',
7556
                    [],
7557
                    true,
7558
                    $form,
7559
                    'directory_parent_id'
7560
                );
7561
7562
                if (isset($data['id'])) {
7563
                    $defaults['directory_parent_id'] = $data['id'];
7564
                }
7565
7566
                break;
7567
        }
7568
7569
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7570
7571
        return $form->returnForm();
7572
    }
7573
7574
    /**
7575
     * @param array  $courseInfo
7576
     * @param string $content
7577
     * @param string $title
7578
     * @param int    $parentId
7579
     *
7580
     * @throws \Doctrine\ORM\ORMException
7581
     * @throws \Doctrine\ORM\OptimisticLockException
7582
     * @throws \Doctrine\ORM\TransactionRequiredException
7583
     *
7584
     * @return int
7585
     */
7586
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7587
    {
7588
        $creatorId = api_get_user_id();
7589
        $sessionId = api_get_session_id();
7590
7591
        // Generates folder
7592
        $result = $this->generate_lp_folder($courseInfo);
7593
        $dir = $result['dir'];
7594
7595
        if (empty($parentId) || '/' == $parentId) {
7596
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7597
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7598
7599
            if ('/' === $parentId) {
7600
                $dir = '/';
7601
            }
7602
7603
            // Please, do not modify this dirname formatting.
7604
            if (strstr($dir, '..')) {
7605
                $dir = '/';
7606
            }
7607
7608
            if (!empty($dir[0]) && '.' == $dir[0]) {
7609
                $dir = substr($dir, 1);
7610
            }
7611
            if (!empty($dir[0]) && '/' != $dir[0]) {
7612
                $dir = '/'.$dir;
7613
            }
7614
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7615
                $dir .= '/';
7616
            }
7617
        } else {
7618
            $parentInfo = DocumentManager::get_document_data_by_id(
7619
                $parentId,
7620
                $courseInfo['code']
7621
            );
7622
            if (!empty($parentInfo)) {
7623
                $dir = $parentInfo['path'].'/';
7624
            }
7625
        }
7626
7627
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7628
7629
        if (!is_dir($filepath)) {
7630
            $dir = '/';
7631
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7632
        }
7633
7634
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7635
7636
        if (!empty($title)) {
7637
            $title = api_replace_dangerous_char(stripslashes($title));
7638
        } else {
7639
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7640
        }
7641
7642
        $title = disable_dangerous_file($title);
7643
        $filename = $title;
7644
        $content = !empty($content) ? $content : $_POST['content_lp'];
7645
        $tmpFileName = $filename;
7646
7647
        $i = 0;
7648
        while (file_exists($filepath.$tmpFileName.'.html')) {
7649
            $tmpFileName = $filename.'_'.++$i;
7650
        }
7651
7652
        $filename = $tmpFileName.'.html';
7653
        $content = stripslashes($content);
7654
7655
        if (file_exists($filepath.$filename)) {
7656
            return 0;
7657
        }
7658
7659
        $putContent = file_put_contents($filepath.$filename, $content);
7660
7661
        if (false === $putContent) {
7662
            return 0;
7663
        }
7664
7665
        $fileSize = filesize($filepath.$filename);
7666
        $saveFilePath = $dir.$filename;
7667
7668
        $document = DocumentManager::addDocument(
7669
            $courseInfo,
7670
            $saveFilePath,
7671
            'file',
7672
            $fileSize,
7673
            $tmpFileName,
7674
            '',
7675
            0, //readonly
7676
            true,
7677
            null,
7678
            $sessionId,
7679
            $creatorId
7680
        );
7681
7682
        $documentId = $document->getId();
7683
7684
        if (!$document) {
7685
            return 0;
7686
        }
7687
7688
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7689
        $newTitle = $originalTitle;
7690
7691
        if ($newComment || $newTitle) {
7692
            $em = Database::getManager();
7693
7694
            if ($newComment) {
7695
                $document->setComment($newComment);
7696
            }
7697
7698
            if ($newTitle) {
7699
                $document->setTitle($newTitle);
7700
            }
7701
7702
            $em->persist($document);
7703
            $em->flush();
7704
        }
7705
7706
        return $documentId;
7707
    }
7708
7709
    /**
7710
     * Displays the menu for manipulating a step.
7711
     *
7712
     * @return string
7713
     */
7714
    public function displayItemMenu(CLpItem $lpItem)
7715
    {
7716
        $item_id = $lpItem->getIid();
7717
        $audio = $lpItem->getAudio();
7718
        $itemType = $lpItem->getItemType();
7719
        $path = $lpItem->getPath();
7720
7721
        $return = '<div class="actions">';
7722
        $audio_player = null;
7723
        // We display an audio player if needed.
7724
        if (!empty($audio)) {
7725
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7726
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7727
                .'<audio src="'.$webAudioPath.'" controls>'
7728
                .'</div><br>';*/
7729
        }
7730
7731
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7732
7733
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7734
            $return .= Display::url(
7735
                Display::return_icon(
7736
                    'edit.png',
7737
                    get_lang('Edit'),
7738
                    [],
7739
                    ICON_SIZE_SMALL
7740
                ),
7741
                $url.'&action=edit_item&path_item='.$path
7742
            );
7743
7744
            /*$return .= Display::url(
7745
                Display::return_icon(
7746
                    'move.png',
7747
                    get_lang('Move'),
7748
                    [],
7749
                    ICON_SIZE_SMALL
7750
                ),
7751
                $url.'&action=move_item'
7752
            );*/
7753
        }
7754
7755
        // Commented for now as prerequisites cannot be added to chapters.
7756
        if ('dir' !== $itemType) {
7757
            $return .= Display::url(
7758
                Display::return_icon(
7759
                    'accept.png',
7760
                    get_lang('Prerequisites'),
7761
                    [],
7762
                    ICON_SIZE_SMALL
7763
                ),
7764
                $url.'&action=edit_item_prereq'
7765
            );
7766
        }
7767
        $return .= Display::url(
7768
            Display::return_icon(
7769
                'delete.png',
7770
                get_lang('Delete'),
7771
                [],
7772
                ICON_SIZE_SMALL
7773
            ),
7774
            $url.'&action=delete_item'
7775
        );
7776
7777
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7778
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7779
            if (empty($documentData)) {
7780
                // Try with iid
7781
                $table = Database::get_course_table(TABLE_DOCUMENT);
7782
                $sql = "SELECT path FROM $table
7783
                        WHERE
7784
                              c_id = ".api_get_course_int_id()." AND
7785
                              iid = ".$path." AND
7786
                              path NOT LIKE '%_DELETED_%'";
7787
                $result = Database::query($sql);
7788
                $documentData = Database::fetch_array($result);
7789
                if ($documentData) {
7790
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7791
                }
7792
            }
7793
            if (isset($documentData['absolute_path_from_document'])) {
7794
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7795
            }
7796
        }*/
7797
7798
        $return .= '</div>';
7799
7800
        if (!empty($audio_player)) {
7801
            $return .= $audio_player;
7802
        }
7803
7804
        return $return;
7805
    }
7806
7807
    /**
7808
     * Creates the javascript needed for filling up the checkboxes without page reload.
7809
     *
7810
     * @return string
7811
     */
7812
    public function get_js_dropdown_array()
7813
    {
7814
        $course_id = api_get_course_int_id();
7815
        $return = 'var child_name = new Array();'."\n";
7816
        $return .= 'var child_value = new Array();'."\n\n";
7817
        $return .= 'child_name[0] = new Array();'."\n";
7818
        $return .= 'child_value[0] = new Array();'."\n\n";
7819
7820
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7821
        $sql = "SELECT * FROM ".$tbl_lp_item."
7822
                WHERE
7823
                    c_id = $course_id AND
7824
                    lp_id = ".$this->lp_id." AND
7825
                    parent_item_id = 0
7826
                ORDER BY display_order ASC";
7827
        Database::query($sql);
7828
        $i = 0;
7829
7830
        $list = $this->getItemsForForm(true);
7831
7832
        foreach ($list as $row_zero) {
7833
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7834
                if (TOOL_QUIZ == $row_zero['item_type']) {
7835
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7836
                }
7837
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7838
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7839
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7840
            }
7841
        }
7842
7843
        $return .= "\n";
7844
        $sql = "SELECT * FROM $tbl_lp_item
7845
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7846
        $res = Database::query($sql);
7847
        while ($row = Database::fetch_array($res)) {
7848
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7849
                           WHERE
7850
                                c_id = ".$course_id." AND
7851
                                parent_item_id = ".$row['iid']."
7852
                           ORDER BY display_order ASC";
7853
            $res_parent = Database::query($sql_parent);
7854
            $i = 0;
7855
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7856
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7857
7858
            while ($row_parent = Database::fetch_array($res_parent)) {
7859
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7860
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7861
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7862
            }
7863
            $return .= "\n";
7864
        }
7865
7866
        $return .= "
7867
            function load_cbo(id) {
7868
                if (!id) {
7869
                    return false;
7870
                }
7871
7872
                var cbo = document.getElementById('previous');
7873
                for(var i = cbo.length - 1; i > 0; i--) {
7874
                    cbo.options[i] = null;
7875
                }
7876
7877
                var k=0;
7878
                for(var i = 1; i <= child_name[id].length; i++){
7879
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7880
                    option.style.paddingLeft = '40px';
7881
                    cbo.options[i] = option;
7882
                    k = i;
7883
                }
7884
7885
                cbo.options[k].selected = true;
7886
                $('#previous').selectpicker('refresh');
7887
            }";
7888
7889
        return $return;
7890
    }
7891
7892
    /**
7893
     * Display the form to allow moving an item.
7894
     *
7895
     * @param CLpItem $lpItem
7896
     *
7897
     * @throws Exception
7898
     * @throws HTML_QuickForm_Error
7899
     *
7900
     * @return string HTML form
7901
     */
7902
    public function display_move_item($lpItem)
7903
    {
7904
        $return = '';
7905
        $path = $lpItem->getPath();
7906
7907
        if ($lpItem) {
7908
            $itemType = $lpItem->getItemType();
7909
            switch ($itemType) {
7910
                case 'dir':
7911
                case 'asset':
7912
                    $return .= $this->displayItemMenu($lpItem);
7913
                    $return .= $this->display_item_form(
7914
                        $lpItem,
7915
                        get_lang('Move the current section'),
7916
                        'move',
7917
                        $row
7918
                    );
7919
                    break;
7920
                case TOOL_DOCUMENT:
7921
                    $return .= $this->displayItemMenu($lpItem);
7922
                    $return .= $this->displayDocumentForm('move', $lpItem);
7923
                    break;
7924
                case TOOL_LINK:
7925
                    $link = null;
7926
                    if (!empty($path)) {
7927
                        $repo = Container::getLinkRepository();
7928
                        $link = $repo->find($path);
7929
                    }
7930
                    $return .= $this->displayItemMenu($lpItem);
7931
                    $return .= $this->display_link_form('move', $lpItem, $link);
7932
                    break;
7933
                case TOOL_HOTPOTATOES:
7934
                    $return .= $this->displayItemMenu($lpItem);
7935
                    $return .= $this->display_link_form('move', $lpItem, $row);
7936
                    break;
7937
                case TOOL_QUIZ:
7938
                    $return .= $this->displayItemMenu($lpItem);
7939
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7940
                    break;
7941
                case TOOL_STUDENTPUBLICATION:
7942
                    $return .= $this->displayItemMenu($lpItem);
7943
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7944
                    break;
7945
                case TOOL_FORUM:
7946
                    $return .= $this->displayItemMenu($lpItem);
7947
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7948
                    break;
7949
                case TOOL_THREAD:
7950
                    $return .= $this->displayItemMenu($lpItem);
7951
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7952
                    break;
7953
            }
7954
        }
7955
7956
        return $return;
7957
    }
7958
7959
    /**
7960
     * Return HTML form to allow prerequisites selection.
7961
     *
7962
     * @todo use FormValidator
7963
     *
7964
     * @return string HTML form
7965
     */
7966
    public function display_item_prerequisites_form(CLpItem $lpItem)
7967
    {
7968
        $course_id = api_get_course_int_id();
7969
        $prerequisiteId = $lpItem->getPrerequisite();
7970
        $itemId = $lpItem->getIid();
7971
7972
        $return = '<legend>';
7973
        $return .= get_lang('Add/edit prerequisites');
7974
        $return .= '</legend>';
7975
        $return .= '<form method="POST">';
7976
        $return .= '<div class="table-responsive">';
7977
        $return .= '<table class="table table-hover">';
7978
        $return .= '<thead>';
7979
        $return .= '<tr>';
7980
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7981
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7982
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7983
        $return .= '</tr>';
7984
        $return .= '</thead>';
7985
7986
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7987
        $return .= '<tbody>';
7988
        $return .= '<tr>';
7989
        $return .= '<td colspan="3">';
7990
        $return .= '<div class="radio learnpath"><label for="idnone">';
7991
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7992
        $return .= get_lang('none').'</label>';
7993
        $return .= '</div>';
7994
        $return .= '</tr>';
7995
7996
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7997
        $sql = "SELECT * FROM $tbl_lp_item
7998
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7999
        $result = Database::query($sql);
8000
8001
        $selectedMinScore = [];
8002
        $selectedMaxScore = [];
8003
        $masteryScore = [];
8004
        while ($row = Database::fetch_array($result)) {
8005
            if ($row['iid'] == $itemId) {
8006
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
8007
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
8008
            }
8009
            $masteryScore[$row['iid']] = $row['mastery_score'];
8010
        }
8011
8012
        $arrLP = $this->getItemsForForm();
8013
        $this->tree_array($arrLP);
8014
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8015
        unset($this->arrMenu);
8016
8017
        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...
8018
            $item = $arrLP[$i];
8019
8020
            if ($item['id'] == $itemId) {
8021
                break;
8022
            }
8023
8024
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
8025
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
8026
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
8027
8028
            $return .= '<tr>';
8029
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
8030
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
8031
            $return .= '<label for="id'.$item['id'].'">';
8032
8033
            $checked = '';
8034
            if (null !== $prerequisiteId) {
8035
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
8036
            }
8037
8038
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
8039
8040
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
8041
8042
            $icon_name = str_replace(' ', '', $item['item_type']);
8043
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
8044
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
8045
            } else {
8046
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
8047
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
8048
                } else {
8049
                    $return .= Display::return_icon('folder_document.png');
8050
                }
8051
            }
8052
8053
            $return .= $item['title'].'</label>';
8054
            $return .= '</div>';
8055
            $return .= '</td>';
8056
8057
            if (TOOL_QUIZ == $item['item_type']) {
8058
                // lets update max_score Tests information depending of the Tests Advanced properties
8059
                $lpItemObj = new LpItem($course_id, $item['id']);
8060
                $exercise = new Exercise($course_id);
8061
                $exercise->read($lpItemObj->path);
8062
                $lpItemObj->max_score = $exercise->get_max_score();
8063
                $lpItemObj->update();
8064
                $item['max_score'] = $lpItemObj->max_score;
8065
8066
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
8067
                    // Backwards compatibility with 1.9.x use mastery_score as min value
8068
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
8069
                }
8070
8071
                $return .= '<td>';
8072
                $return .= '<input
8073
                    class="form-control"
8074
                    size="4" maxlength="3"
8075
                    name="min_'.$item['id'].'"
8076
                    type="number"
8077
                    min="0"
8078
                    step="any"
8079
                    max="'.$item['max_score'].'"
8080
                    value="'.$selectedMinScoreValue.'"
8081
                />';
8082
                $return .= '</td>';
8083
                $return .= '<td>';
8084
                $return .= '<input
8085
                    class="form-control"
8086
                    size="4"
8087
                    maxlength="3"
8088
                    name="max_'.$item['id'].'"
8089
                    type="number"
8090
                    min="0"
8091
                    step="any"
8092
                    max="'.$item['max_score'].'"
8093
                    value="'.$selectedMaxScoreValue.'"
8094
                />';
8095
                $return .= '</td>';
8096
            }
8097
8098
            if (TOOL_HOTPOTATOES == $item['item_type']) {
8099
                $return .= '<td>';
8100
                $return .= '<input
8101
                    size="4"
8102
                    maxlength="3"
8103
                    name="min_'.$item['id'].'"
8104
                    type="number"
8105
                    min="0"
8106
                    step="any"
8107
                    max="'.$item['max_score'].'"
8108
                    value="'.$selectedMinScoreValue.'"
8109
                />';
8110
                $return .= '</td>';
8111
                $return .= '<td>';
8112
                $return .= '<input
8113
                    size="4"
8114
                    maxlength="3"
8115
                    name="max_'.$item['id'].'"
8116
                    type="number"
8117
                    min="0"
8118
                    step="any"
8119
                    max="'.$item['max_score'].'"
8120
                    value="'.$selectedMaxScoreValue.'"
8121
                />';
8122
                $return .= '</td>';
8123
            }
8124
            $return .= '</tr>';
8125
        }
8126
        $return .= '<tr>';
8127
        $return .= '</tr>';
8128
        $return .= '</tbody>';
8129
        $return .= '</table>';
8130
        $return .= '</div>';
8131
        $return .= '<div class="form-group">';
8132
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8133
            get_lang('Save prerequisites settings').'</button>';
8134
        $return .= '</form>';
8135
8136
        return $return;
8137
    }
8138
8139
    /**
8140
     * Return HTML list to allow prerequisites selection for lp.
8141
     *
8142
     * @return string HTML form
8143
     */
8144
    public function display_lp_prerequisites_list()
8145
    {
8146
        $course_id = api_get_course_int_id();
8147
        $lp_id = $this->lp_id;
8148
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8149
8150
        // get current prerequisite
8151
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8152
        $result = Database::query($sql);
8153
        $row = Database::fetch_array($result);
8154
        $prerequisiteId = $row['prerequisite'];
8155
        $session_id = api_get_session_id();
8156
        $session_condition = api_get_session_condition($session_id, true, true);
8157
        $sql = "SELECT * FROM $tbl_lp
8158
                WHERE c_id = $course_id $session_condition
8159
                ORDER BY display_order ";
8160
        $rs = Database::query($sql);
8161
        $return = '';
8162
        $return .= '<select name="prerequisites" class="form-control">';
8163
        $return .= '<option value="0">'.get_lang('none').'</option>';
8164
        if (Database::num_rows($rs) > 0) {
8165
            while ($row = Database::fetch_array($rs)) {
8166
                if ($row['id'] == $lp_id) {
8167
                    continue;
8168
                }
8169
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
8170
            }
8171
        }
8172
        $return .= '</select>';
8173
8174
        return $return;
8175
    }
8176
8177
    /**
8178
     * Creates a list with all the documents in it.
8179
     *
8180
     * @param bool $showInvisibleFiles
8181
     *
8182
     * @throws Exception
8183
     * @throws HTML_QuickForm_Error
8184
     *
8185
     * @return string
8186
     */
8187
    public function get_documents($showInvisibleFiles = false)
8188
    {
8189
        $course_info = api_get_course_info();
8190
        $sessionId = api_get_session_id();
8191
        $documentTree = DocumentManager::get_document_preview(
8192
            $course_info,
8193
            $this->lp_id,
8194
            null,
8195
            $sessionId,
8196
            true,
8197
            null,
8198
            null,
8199
            $showInvisibleFiles,
8200
            true
8201
        );
8202
8203
        $headers = [
8204
            get_lang('Files'),
8205
            get_lang('CreateTheDocument'),
8206
            get_lang('CreateReadOutText'),
8207
            get_lang('Upload'),
8208
        ];
8209
8210
        $form = new FormValidator(
8211
            'form_upload',
8212
            'POST',
8213
            $this->getCurrentBuildingModeURL(),
8214
            '',
8215
            ['enctype' => 'multipart/form-data']
8216
        );
8217
8218
        $folders = DocumentManager::get_all_document_folders(
8219
            api_get_course_info(),
8220
            0,
8221
            true
8222
        );
8223
8224
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8225
8226
        DocumentManager::build_directory_selector(
8227
            $folders,
8228
            $lpPathInfo['id'],
8229
            [],
8230
            true,
8231
            $form,
8232
            'directory_parent_id'
8233
        );
8234
8235
        $group = [
8236
            $form->createElement(
8237
                'radio',
8238
                'if_exists',
8239
                get_lang('If file exists:'),
8240
                get_lang('Do nothing'),
8241
                'nothing'
8242
            ),
8243
            $form->createElement(
8244
                'radio',
8245
                'if_exists',
8246
                null,
8247
                get_lang('Overwrite the existing file'),
8248
                'overwrite'
8249
            ),
8250
            $form->createElement(
8251
                'radio',
8252
                'if_exists',
8253
                null,
8254
                get_lang('Rename the uploaded file if it exists'),
8255
                'rename'
8256
            ),
8257
        ];
8258
        $form->addGroup($group, null, get_lang('If file exists:'));
8259
8260
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8261
        $defaultFileExistsOption = 'rename';
8262
        if (!empty($fileExistsOption)) {
8263
            $defaultFileExistsOption = $fileExistsOption;
8264
        }
8265
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8266
8267
        // Check box options
8268
        $form->addElement(
8269
            'checkbox',
8270
            'unzip',
8271
            get_lang('Options'),
8272
            get_lang('Uncompress zip')
8273
        );
8274
8275
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8276
        $form->addMultipleUpload($url);
8277
8278
        $lpItem = new CLpItem();
8279
        $lpItem->setItemType(TOOL_DOCUMENT);
8280
        $new = $this->displayDocumentForm('add', $lpItem);
8281
8282
        /*$lpItem = new CLpItem();
8283
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8284
        $frmReadOutText = $this->displayDocumentForm('add');*/
8285
8286
        $headers = [
8287
            get_lang('Files'),
8288
            get_lang('Create a new document'),
8289
            get_lang('Create read-out text'),
8290
            get_lang('Upload'),
8291
        ];
8292
8293
        return Display::tabs(
8294
            $headers,
8295
            [$documentTree, $new, $form->returnForm()],
8296
            'subtab'
8297
        );
8298
    }
8299
8300
    /**
8301
     * Creates a list with all the exercises (quiz) in it.
8302
     *
8303
     * @return string
8304
     */
8305
    public function get_exercises()
8306
    {
8307
        $course_id = api_get_course_int_id();
8308
        $session_id = api_get_session_id();
8309
        $userInfo = api_get_user_info();
8310
8311
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8312
        $condition_session = api_get_session_condition($session_id, true, true);
8313
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8314
8315
        $activeCondition = ' active <> -1 ';
8316
        if ($setting) {
8317
            $activeCondition = ' active = 1 ';
8318
        }
8319
8320
        $categoryCondition = '';
8321
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8322
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8323
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8324
        }
8325
8326
        $keywordCondition = '';
8327
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8328
8329
        if (!empty($keyword)) {
8330
            $keyword = Database::escape_string($keyword);
8331
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8332
        }
8333
8334
        $sql_quiz = "SELECT * FROM $tbl_quiz
8335
                     WHERE
8336
                            c_id = $course_id AND
8337
                            $activeCondition
8338
                            $condition_session
8339
                            $categoryCondition
8340
                            $keywordCondition
8341
                     ORDER BY title ASC";
8342
        $res_quiz = Database::query($sql_quiz);
8343
8344
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8345
8346
        // Create a search-box
8347
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8348
        $form->addHidden('action', 'add_item');
8349
        $form->addHidden('type', 'step');
8350
        $form->addHidden('lp_id', $this->lp_id);
8351
        $form->addHidden('lp_build_selected', '2');
8352
8353
        $form->addCourseHiddenParams();
8354
        $form->addText(
8355
            'keyword',
8356
            get_lang('Search'),
8357
            false,
8358
            [
8359
                'aria-label' => get_lang('Search'),
8360
            ]
8361
        );
8362
8363
        if (api_get_configuration_value('allow_exercise_categories')) {
8364
            $manager = new ExerciseCategoryManager();
8365
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8366
            if (!empty($options)) {
8367
                $form->addSelect(
8368
                    'category_id',
8369
                    get_lang('Category'),
8370
                    $options,
8371
                    ['placeholder' => get_lang('Please select an option')]
8372
                );
8373
            }
8374
        }
8375
8376
        $form->addButtonSearch(get_lang('Search'));
8377
        $return = $form->returnForm();
8378
8379
        $return .= '<ul class="lp_resource">';
8380
        $return .= '<li class="lp_resource_element">';
8381
        $return .= Display::return_icon('new_exercice.png');
8382
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8383
            get_lang('New test').'</a>';
8384
        $return .= '</li>';
8385
8386
        $previewIcon = Display::return_icon(
8387
            'preview_view.png',
8388
            get_lang('Preview')
8389
        );
8390
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8391
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8392
8393
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8394
        $repo = Container::getExerciseRepository();
8395
        $courseEntity = api_get_course_entity();
8396
        $sessionEntity = api_get_session_entity();
8397
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8398
            /** @var CQuiz $exercise */
8399
            $exercise = $repo->find($row_quiz['id']);
8400
            $title = strip_tags(
8401
                api_html_entity_decode($row_quiz['title'])
8402
            );
8403
8404
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8405
            /*$visibility = api_get_item_visibility(
8406
                ['real_id' => $course_id],
8407
                TOOL_QUIZ,
8408
                $row_quiz['iid'],
8409
                $session_id
8410
            );*/
8411
8412
            $link = Display::url(
8413
                $previewIcon,
8414
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
8415
                ['target' => '_blank']
8416
            );
8417
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
8418
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8419
            $return .= $quizIcon;
8420
            $sessionStar = api_get_session_image(
8421
                $row_quiz['session_id'],
8422
                $userInfo['status']
8423
            );
8424
            $return .= Display::url(
8425
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8426
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
8427
                [
8428
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8429
                ]
8430
            );
8431
            $return .= '</li>';
8432
        }
8433
8434
        $return .= '</ul>';
8435
8436
        return $return;
8437
    }
8438
8439
    /**
8440
     * Creates a list with all the links in it.
8441
     *
8442
     * @return string
8443
     */
8444
    public function get_links()
8445
    {
8446
        $sessionId = api_get_session_id();
8447
        $repo = Container::getLinkRepository();
8448
8449
        $course = api_get_course_entity();
8450
        $session = api_get_session_entity($sessionId);
8451
        $qb = $repo->getResourcesByCourse($course, $session);
8452
        /** @var CLink[] $links */
8453
        $links = $qb->getQuery()->getResult();
8454
8455
        $selfUrl = api_get_self();
8456
        $courseIdReq = api_get_cidreq();
8457
        $userInfo = api_get_user_info();
8458
8459
        $moveEverywhereIcon = Display::return_icon(
8460
            'move_everywhere.png',
8461
            get_lang('Move'),
8462
            [],
8463
            ICON_SIZE_TINY
8464
        );
8465
8466
        /*$condition_session = api_get_session_condition(
8467
            $session_id,
8468
            true,
8469
            true,
8470
            'link.session_id'
8471
        );
8472
8473
        $sql = "SELECT
8474
                    link.id as link_id,
8475
                    link.title as link_title,
8476
                    link.session_id as link_session_id,
8477
                    link.category_id as category_id,
8478
                    link_category.category_title as category_title
8479
                FROM $tbl_link as link
8480
                LEFT JOIN $linkCategoryTable as link_category
8481
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8482
                WHERE link.c_id = $course_id $condition_session
8483
                ORDER BY link_category.category_title ASC, link.title ASC";
8484
        $result = Database::query($sql);*/
8485
        $categorizedLinks = [];
8486
        $categories = [];
8487
8488
        foreach ($links as $link) {
8489
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8490
8491
            if (empty($categoryId)) {
8492
                $categories[0] = get_lang('Uncategorized');
8493
            } else {
8494
                $category = $link->getCategory();
8495
                $categories[$categoryId] = $category->getCategoryTitle();
8496
            }
8497
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8498
        }
8499
8500
        $linksHtmlCode =
8501
            '<script>
8502
            function toggle_tool(tool, id) {
8503
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8504
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8505
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8506
                } else {
8507
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8508
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8509
                }
8510
            }
8511
        </script>
8512
8513
        <ul class="lp_resource">
8514
            <li class="lp_resource_element">
8515
                '.Display::return_icon('linksnew.gif').'
8516
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
8517
                get_lang('Add a link').'
8518
                </a>
8519
            </li>';
8520
8521
        foreach ($categorizedLinks as $categoryId => $links) {
8522
            $linkNodes = null;
8523
            /** @var CLink $link */
8524
            foreach ($links as $key => $link) {
8525
                $title = $link->getTitle();
8526
                $linkSessionId = $link->getSessionId();
8527
8528
                $linkUrl = Display::url(
8529
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8530
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8531
                    ['target' => '_blank']
8532
                );
8533
8534
                if ($link->isVisible($course, $session)) {
8535
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8536
                    $linkNodes .=
8537
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8538
                        <a class="moved" href="#">'.
8539
                            $moveEverywhereIcon.
8540
                        '</a>
8541
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8542
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8543
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8544
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8545
                        '</a>
8546
                    </li>';
8547
                }
8548
            }
8549
            $linksHtmlCode .=
8550
                '<li>
8551
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8552
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8553
                    align="absbottom" />
8554
                </a>
8555
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8556
            </li>
8557
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8558
        }
8559
        $linksHtmlCode .= '</ul>';
8560
8561
        return $linksHtmlCode;
8562
    }
8563
8564
    /**
8565
     * Creates a list with all the student publications in it.
8566
     *
8567
     * @return string
8568
     */
8569
    public function get_student_publications()
8570
    {
8571
        $return = '<ul class="lp_resource">';
8572
        $return .= '<li class="lp_resource_element">';
8573
        /*
8574
        $return .= Display::return_icon('works_new.gif');
8575
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8576
            get_lang('Add the Assignments tool to the course').'</a>';
8577
        $return .= '</li>';*/
8578
8579
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8580
        $works = getWorkListTeacher(0, 100, null, null, null);
8581
        if (!empty($works)) {
8582
            foreach ($works as $work) {
8583
                $link = Display::url(
8584
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8585
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8586
                    ['target' => '_blank']
8587
                );
8588
8589
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8590
                $return .= '<a class="moved" href="#">';
8591
                $return .= Display::return_icon(
8592
                    'move_everywhere.png',
8593
                    get_lang('Move'),
8594
                    [],
8595
                    ICON_SIZE_TINY
8596
                );
8597
                $return .= '</a> ';
8598
8599
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8600
                $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
8601
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8602
                </a>';
8603
8604
                $return .= '</li>';
8605
            }
8606
        }
8607
8608
        $return .= '</ul>';
8609
8610
        return $return;
8611
    }
8612
8613
    /**
8614
     * Creates a list with all the forums in it.
8615
     *
8616
     * @return string
8617
     */
8618
    public function get_forums()
8619
    {
8620
        require_once '../forum/forumfunction.inc.php';
8621
8622
        $forumCategories = get_forum_categories();
8623
        $forumsInNoCategory = get_forums_in_category(0);
8624
        if (!empty($forumsInNoCategory)) {
8625
            $forumCategories = array_merge(
8626
                $forumCategories,
8627
                [
8628
                    [
8629
                        'cat_id' => 0,
8630
                        'session_id' => 0,
8631
                        'visibility' => 1,
8632
                        'cat_comment' => null,
8633
                    ],
8634
                ]
8635
            );
8636
        }
8637
8638
        $a_forums = [];
8639
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8640
        $sessionEntity = api_get_session_entity(api_get_session_id());
8641
8642
        foreach ($forumCategories as $forumCategory) {
8643
            // The forums in this category.
8644
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8645
            if (!empty($forumsInCategory)) {
8646
                foreach ($forumsInCategory as $forum) {
8647
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8648
                        $a_forums[] = $forum;
8649
                    }
8650
                }
8651
            }
8652
        }
8653
8654
        $return = '<ul class="lp_resource">';
8655
8656
        // First add link
8657
        $return .= '<li class="lp_resource_element">';
8658
        $return .= Display::return_icon('new_forum.png');
8659
        $return .= Display::url(
8660
            get_lang('Create a new forum'),
8661
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8662
                'action' => 'add',
8663
                'content' => 'forum',
8664
                'lp_id' => $this->lp_id,
8665
            ]),
8666
            ['title' => get_lang('Create a new forum')]
8667
        );
8668
        $return .= '</li>';
8669
8670
        $return .= '<script>
8671
            function toggle_forum(forum_id) {
8672
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8673
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8674
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8675
                } else {
8676
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8677
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8678
                }
8679
            }
8680
        </script>';
8681
8682
        foreach ($a_forums as $forum) {
8683
            $forumId = $forum->getIid();
8684
            $title = Security::remove_XSS($forum->getForumTitle());
8685
            $link = Display::url(
8686
                Display::return_icon('preview_view.png', get_lang('Preview')),
8687
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8688
                ['target' => '_blank']
8689
            );
8690
8691
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8692
            $return .= '<a class="moved" href="#">';
8693
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8694
            $return .= ' </a>';
8695
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8696
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8697
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8698
                        </a>
8699
                        <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
8700
                $title.' '.$link.'</a>';
8701
8702
            $return .= '</li>';
8703
8704
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8705
            $threads = get_threads($forumId);
8706
            if (is_array($threads)) {
8707
                foreach ($threads as $thread) {
8708
                    $threadId = $thread->getIid();
8709
                    $link = Display::url(
8710
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8711
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8712
                        ['target' => '_blank']
8713
                    );
8714
8715
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8716
                    $return .= '&nbsp;<a class="moved" href="#">';
8717
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8718
                    $return .= ' </a>';
8719
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8720
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
8721
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8722
                    $return .= '</li>';
8723
                }
8724
            }
8725
            $return .= '</div>';
8726
        }
8727
        $return .= '</ul>';
8728
8729
        return $return;
8730
    }
8731
8732
    /**
8733
     * // TODO: The output encoding should be equal to the system encoding.
8734
     *
8735
     * Exports the learning path as a SCORM package. This is the main function that
8736
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8737
     * whole thing and returns the zip.
8738
     *
8739
     * This method needs to be called in PHP5, as it will fail with non-adequate
8740
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8741
     * you need to call it on a learnpath object.
8742
     *
8743
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8744
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8745
     * path has been modified, it should use the generic method here below.
8746
     *
8747
     * @return string Returns the zip package string, or null if error
8748
     */
8749
    public function scormExport()
8750
    {
8751
        api_set_more_memory_and_time_limits();
8752
8753
        $_course = api_get_course_info();
8754
        $course_id = $_course['real_id'];
8755
        // Create the zip handler (this will remain available throughout the method).
8756
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8757
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8758
        $temp_dir_short = uniqid('scorm_export', true);
8759
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8760
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8761
        $zip_folder = new PclZip($temp_zip_file);
8762
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8763
        $root_path = $main_path = api_get_path(SYS_PATH);
8764
        $files_cleanup = [];
8765
8766
        // Place to temporarily stash the zip file.
8767
        // create the temp dir if it doesn't exist
8768
        // or do a cleanup before creating the zip file.
8769
        if (!is_dir($temp_zip_dir)) {
8770
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8771
        } else {
8772
            // Cleanup: Check the temp dir for old files and delete them.
8773
            $handle = opendir($temp_zip_dir);
8774
            while (false !== ($file = readdir($handle))) {
8775
                if ('.' != $file && '..' != $file) {
8776
                    unlink("$temp_zip_dir/$file");
8777
                }
8778
            }
8779
            closedir($handle);
8780
        }
8781
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8782
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8783
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8784
        ) {
8785
            // Remove the possible . at the end of the path.
8786
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8787
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8788
            mkdir(
8789
                $dest_path_to_scorm_folder,
8790
                api_get_permissions_for_new_directories(),
8791
                true
8792
            );
8793
            copyr(
8794
                $current_course_path.'/scorm/'.$this->path,
8795
                $dest_path_to_scorm_folder,
8796
                ['imsmanifest'],
8797
                $zip_files
8798
            );
8799
        }
8800
8801
        // Build a dummy imsmanifest structure.
8802
        // Do not add to the zip yet (we still need it).
8803
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8804
        // Aggregation Model official document, section "2.3 Content Packaging".
8805
        // We are going to build a UTF-8 encoded manifest.
8806
        // Later we will recode it to the desired (and supported) encoding.
8807
        $xmldoc = new DOMDocument('1.0');
8808
        $root = $xmldoc->createElement('manifest');
8809
        $root->setAttribute('identifier', 'SingleCourseManifest');
8810
        $root->setAttribute('version', '1.1');
8811
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8812
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8813
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8814
        $root->setAttribute(
8815
            'xsi:schemaLocation',
8816
            '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'
8817
        );
8818
        // Build mandatory sub-root container elements.
8819
        $metadata = $xmldoc->createElement('metadata');
8820
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8821
        $metadata->appendChild($md_schema);
8822
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8823
        $metadata->appendChild($md_schemaversion);
8824
        $root->appendChild($metadata);
8825
8826
        $organizations = $xmldoc->createElement('organizations');
8827
        $resources = $xmldoc->createElement('resources');
8828
8829
        // Build the only organization we will use in building our learnpaths.
8830
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8831
        $organization = $xmldoc->createElement('organization');
8832
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8833
        // To set the title of the SCORM entity (=organization), we take the name given
8834
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8835
        // learning path charset) as it is the encoding that defines how it is stored
8836
        // in the database. Then we convert it to HTML entities again as the "&" character
8837
        // alone is not authorized in XML (must be &amp;).
8838
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8839
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8840
        $organization->appendChild($org_title);
8841
        $folder_name = 'document';
8842
8843
        // Removes the learning_path/scorm_folder path when exporting see #4841
8844
        $path_to_remove = '';
8845
        $path_to_replace = '';
8846
        $result = $this->generate_lp_folder($_course);
8847
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8848
            $path_to_remove = 'document'.$result['dir'];
8849
            $path_to_replace = $folder_name.'/';
8850
        }
8851
8852
        // Fixes chamilo scorm exports
8853
        if ('chamilo_scorm_export' === $this->ref) {
8854
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8855
        }
8856
8857
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8858
        $link_updates = [];
8859
        $links_to_create = [];
8860
        foreach ($this->ordered_items as $index => $itemId) {
8861
            /** @var learnpathItem $item */
8862
            $item = $this->items[$itemId];
8863
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8864
                // Get included documents from this item.
8865
                if ('sco' === $item->type) {
8866
                    $inc_docs = $item->get_resources_from_source(
8867
                        null,
8868
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8869
                    );
8870
                } else {
8871
                    $inc_docs = $item->get_resources_from_source();
8872
                }
8873
8874
                // Give a child element <item> to the <organization> element.
8875
                $my_item_id = $item->get_id();
8876
                $my_item = $xmldoc->createElement('item');
8877
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8878
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_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(
8892
                    'adlcp:prerequisites',
8893
                    $this->get_scorm_prereq_string($my_item_id)
8894
                );
8895
                $my_prereqs->setAttribute('type', 'aicc_script');
8896
                $my_item->appendChild($my_prereqs);
8897
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8898
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8899
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8900
                //$xmldoc->createElement('adlcp:timelimitaction','');
8901
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8902
                //$xmldoc->createElement('adlcp:datafromlms','');
8903
                // Give a child element <adlcp:masteryscore> to the <item> element.
8904
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8905
                $my_item->appendChild($my_masteryscore);
8906
8907
                // Attach this item to the organization element or hits parent if there is one.
8908
                if (!empty($item->parent) && 0 != $item->parent) {
8909
                    $children = $organization->childNodes;
8910
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8911
                    if (is_object($possible_parent)) {
8912
                        $possible_parent->appendChild($my_item);
8913
                    } else {
8914
                        if ($this->debug > 0) {
8915
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8916
                        }
8917
                    }
8918
                } else {
8919
                    if ($this->debug > 0) {
8920
                        error_log('No parent');
8921
                    }
8922
                    $organization->appendChild($my_item);
8923
                }
8924
8925
                // Get the path of the file(s) from the course directory root.
8926
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8927
                $my_xml_file_path = $my_file_path;
8928
                if (!empty($path_to_remove)) {
8929
                    // From docs
8930
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8931
8932
                    // From quiz
8933
                    if ('chamilo_scorm_export' === $this->ref) {
8934
                        $path_to_remove = 'scorm/'.$this->path.'/';
8935
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8936
                    }
8937
                }
8938
8939
                $my_sub_dir = dirname($my_file_path);
8940
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8941
                $my_xml_sub_dir = $my_sub_dir;
8942
                // Give a <resource> child to the <resources> element
8943
                $my_resource = $xmldoc->createElement('resource');
8944
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8945
                $my_resource->setAttribute('type', 'webcontent');
8946
                $my_resource->setAttribute('href', $my_xml_file_path);
8947
                // adlcp:scormtype can be either 'sco' or 'asset'.
8948
                if ('sco' === $item->type) {
8949
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8950
                } else {
8951
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8952
                }
8953
                // xml:base is the base directory to find the files declared in this resource.
8954
                $my_resource->setAttribute('xml:base', '');
8955
                // Give a <file> child to the <resource> element.
8956
                $my_file = $xmldoc->createElement('file');
8957
                $my_file->setAttribute('href', $my_xml_file_path);
8958
                $my_resource->appendChild($my_file);
8959
8960
                // Dependency to other files - not yet supported.
8961
                $i = 1;
8962
                if ($inc_docs) {
8963
                    foreach ($inc_docs as $doc_info) {
8964
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8965
                            continue;
8966
                        }
8967
                        $my_dep = $xmldoc->createElement('resource');
8968
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8969
                        $my_dep->setAttribute('identifier', $res_id);
8970
                        $my_dep->setAttribute('type', 'webcontent');
8971
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8972
                        $my_dep_file = $xmldoc->createElement('file');
8973
                        // Check type of URL.
8974
                        if ('remote' == $doc_info[1]) {
8975
                            // Remote file. Save url as is.
8976
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8977
                            $my_dep->setAttribute('xml:base', '');
8978
                        } elseif ('local' === $doc_info[1]) {
8979
                            switch ($doc_info[2]) {
8980
                                case 'url':
8981
                                    // Local URL - save path as url for now, don't zip file.
8982
                                    $abs_path = api_get_path(SYS_PATH).
8983
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8984
                                    $current_dir = dirname($abs_path);
8985
                                    $current_dir = str_replace('\\', '/', $current_dir);
8986
                                    $file_path = realpath($abs_path);
8987
                                    $file_path = str_replace('\\', '/', $file_path);
8988
                                    $my_dep_file->setAttribute('href', $file_path);
8989
                                    $my_dep->setAttribute('xml:base', '');
8990
                                    if (false !== strstr($file_path, $main_path)) {
8991
                                        // The calculated real path is really inside Chamilo's root path.
8992
                                        // Reduce file path to what's under the DocumentRoot.
8993
                                        $replace = $file_path;
8994
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8995
                                        $destinationFile = $file_path;
8996
8997
                                        if (false !== strstr($file_path, 'upload/users')) {
8998
                                            $pos = strpos($file_path, 'my_files/');
8999
                                            if (false !== $pos) {
9000
                                                $onlyDirectory = str_replace(
9001
                                                    'upload/users/',
9002
                                                    '',
9003
                                                    substr($file_path, $pos, strlen($file_path))
9004
                                                );
9005
                                            }
9006
                                            $replace = $onlyDirectory;
9007
                                            $destinationFile = $replace;
9008
                                        }
9009
                                        $zip_files_abs[] = $file_path;
9010
                                        $link_updates[$my_file_path][] = [
9011
                                            'orig' => $doc_info[0],
9012
                                            'dest' => $destinationFile,
9013
                                            'replace' => $replace,
9014
                                        ];
9015
                                        $my_dep_file->setAttribute('href', $file_path);
9016
                                        $my_dep->setAttribute('xml:base', '');
9017
                                    } elseif (empty($file_path)) {
9018
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9019
                                        $file_path = str_replace('//', '/', $file_path);
9020
                                        if (file_exists($file_path)) {
9021
                                            // We get the relative path.
9022
                                            $file_path = substr($file_path, strlen($current_dir));
9023
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
9024
                                            $link_updates[$my_file_path][] = [
9025
                                                'orig' => $doc_info[0],
9026
                                                'dest' => $file_path,
9027
                                            ];
9028
                                            $my_dep_file->setAttribute('href', $file_path);
9029
                                            $my_dep->setAttribute('xml:base', '');
9030
                                        }
9031
                                    }
9032
                                    break;
9033
                                case 'abs':
9034
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9035
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9036
                                    $my_dep->setAttribute('xml:base', '');
9037
9038
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
9039
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
9040
                                    $abs_img_path_without_subdir = $doc_info[0];
9041
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
9042
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
9043
                                    if (0 === $pos) {
9044
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
9045
                                    }
9046
9047
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
9048
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
9049
9050
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
9051
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
9052
                                    // Check if the current document is in that path.
9053
                                    if (false !== strstr($file_path, $cur_path)) {
9054
                                        $destinationFile = substr($file_path, strlen($cur_path));
9055
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
9056
9057
                                        $fileToTest = $cur_path.$my_file_path;
9058
                                        if (!empty($path_to_remove)) {
9059
                                            $fileToTest = str_replace(
9060
                                                $path_to_remove.'/',
9061
                                                $path_to_replace,
9062
                                                $cur_path.$my_file_path
9063
                                            );
9064
                                        }
9065
9066
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
9067
9068
                                        // Put the current document in the zip (this array is the array
9069
                                        // that will manage documents already in the course folder - relative).
9070
                                        $zip_files[] = $filePathNoCoursePart;
9071
                                        // Update the links to the current document in the
9072
                                        // containing document (make them relative).
9073
                                        $link_updates[$my_file_path][] = [
9074
                                            'orig' => $doc_info[0],
9075
                                            'dest' => $destinationFile,
9076
                                            'replace' => $relative_path,
9077
                                        ];
9078
9079
                                        $my_dep_file->setAttribute('href', $file_path);
9080
                                        $my_dep->setAttribute('xml:base', '');
9081
                                    } elseif (false !== strstr($file_path, $main_path)) {
9082
                                        // The calculated real path is really inside Chamilo's root path.
9083
                                        // Reduce file path to what's under the DocumentRoot.
9084
                                        $file_path = substr($file_path, strlen($root_path));
9085
                                        $zip_files_abs[] = $file_path;
9086
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9087
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9088
                                        $my_dep->setAttribute('xml:base', '');
9089
                                    } elseif (empty($file_path)) {
9090
                                        // Probably this is an image inside "/main" directory
9091
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
9092
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9093
9094
                                        if (file_exists($file_path)) {
9095
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
9096
                                                // We get the relative path.
9097
                                                $pos = strpos($file_path, 'main/default_course_document/');
9098
                                                if (false !== $pos) {
9099
                                                    $onlyDirectory = str_replace(
9100
                                                        'main/default_course_document/',
9101
                                                        '',
9102
                                                        substr($file_path, $pos, strlen($file_path))
9103
                                                    );
9104
                                                }
9105
9106
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
9107
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
9108
                                                $zip_files_abs[] = $fileAbs;
9109
                                                $link_updates[$my_file_path][] = [
9110
                                                    'orig' => $doc_info[0],
9111
                                                    'dest' => $destinationFile,
9112
                                                ];
9113
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9114
                                                $my_dep->setAttribute('xml:base', '');
9115
                                            }
9116
                                        }
9117
                                    }
9118
                                    break;
9119
                                case 'rel':
9120
                                    // Path relative to the current document.
9121
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
9122
                                    if ('..' === substr($doc_info[0], 0, 2)) {
9123
                                        // Relative path going up.
9124
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9125
                                        $current_dir = str_replace('\\', '/', $current_dir);
9126
                                        $file_path = realpath($current_dir.$doc_info[0]);
9127
                                        $file_path = str_replace('\\', '/', $file_path);
9128
                                        if (false !== strstr($file_path, $main_path)) {
9129
                                            // The calculated real path is really inside Chamilo's root path.
9130
                                            // Reduce file path to what's under the DocumentRoot.
9131
                                            $file_path = substr($file_path, strlen($root_path));
9132
                                            $zip_files_abs[] = $file_path;
9133
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9134
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9135
                                            $my_dep->setAttribute('xml:base', '');
9136
                                        }
9137
                                    } else {
9138
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9139
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9140
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9141
                                    }
9142
                                    break;
9143
                                default:
9144
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9145
                                    $my_dep->setAttribute('xml:base', '');
9146
                                    break;
9147
                            }
9148
                        }
9149
                        $my_dep->appendChild($my_dep_file);
9150
                        $resources->appendChild($my_dep);
9151
                        $dependency = $xmldoc->createElement('dependency');
9152
                        $dependency->setAttribute('identifierref', $res_id);
9153
                        $my_resource->appendChild($dependency);
9154
                        $i++;
9155
                    }
9156
                }
9157
                $resources->appendChild($my_resource);
9158
                $zip_files[] = $my_file_path;
9159
            } else {
9160
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9161
                switch ($item->type) {
9162
                    case TOOL_LINK:
9163
                        $my_item = $xmldoc->createElement('item');
9164
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9165
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9166
                        $my_item->setAttribute('isvisible', 'true');
9167
                        // Give a child element <title> to the <item> element.
9168
                        $my_title = $xmldoc->createElement(
9169
                            'title',
9170
                            htmlspecialchars(
9171
                                api_utf8_encode($item->get_title()),
9172
                                ENT_QUOTES,
9173
                                'UTF-8'
9174
                            )
9175
                        );
9176
                        $my_item->appendChild($my_title);
9177
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9178
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9179
                        $my_prereqs->setAttribute('type', 'aicc_script');
9180
                        $my_item->appendChild($my_prereqs);
9181
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9182
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9183
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9184
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9185
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9186
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9187
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9188
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9189
                        $my_item->appendChild($my_masteryscore);
9190
9191
                        // Attach this item to the organization element or its parent if there is one.
9192
                        if (!empty($item->parent) && 0 != $item->parent) {
9193
                            $children = $organization->childNodes;
9194
                            for ($i = 0; $i < $children->length; $i++) {
9195
                                $item_temp = $children->item($i);
9196
                                if ('item' == $item_temp->nodeName) {
9197
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9198
                                        $item_temp->appendChild($my_item);
9199
                                    }
9200
                                }
9201
                            }
9202
                        } else {
9203
                            $organization->appendChild($my_item);
9204
                        }
9205
9206
                        $my_file_path = 'link_'.$item->get_id().'.html';
9207
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9208
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9209
                        $rs = Database::query($sql);
9210
                        if ($link = Database::fetch_array($rs)) {
9211
                            $url = $link['url'];
9212
                            $title = stripslashes($link['title']);
9213
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9214
                            $my_xml_file_path = $my_file_path;
9215
                            $my_sub_dir = dirname($my_file_path);
9216
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9217
                            $my_xml_sub_dir = $my_sub_dir;
9218
                            // Give a <resource> child to the <resources> element.
9219
                            $my_resource = $xmldoc->createElement('resource');
9220
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9221
                            $my_resource->setAttribute('type', 'webcontent');
9222
                            $my_resource->setAttribute('href', $my_xml_file_path);
9223
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9224
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9225
                            // xml:base is the base directory to find the files declared in this resource.
9226
                            $my_resource->setAttribute('xml:base', '');
9227
                            // give a <file> child to the <resource> element.
9228
                            $my_file = $xmldoc->createElement('file');
9229
                            $my_file->setAttribute('href', $my_xml_file_path);
9230
                            $my_resource->appendChild($my_file);
9231
                            $resources->appendChild($my_resource);
9232
                        }
9233
                        break;
9234
                    case TOOL_QUIZ:
9235
                        $exe_id = $item->path;
9236
                        // Should be using ref when everything will be cleaned up in this regard.
9237
                        $exe = new Exercise();
9238
                        $exe->read($exe_id);
9239
                        $my_item = $xmldoc->createElement('item');
9240
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9241
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9242
                        $my_item->setAttribute('isvisible', 'true');
9243
                        // Give a child element <title> to the <item> element.
9244
                        $my_title = $xmldoc->createElement(
9245
                            'title',
9246
                            htmlspecialchars(
9247
                                api_utf8_encode($item->get_title()),
9248
                                ENT_QUOTES,
9249
                                'UTF-8'
9250
                            )
9251
                        );
9252
                        $my_item->appendChild($my_title);
9253
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9254
                        $my_item->appendChild($my_max_score);
9255
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9256
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9257
                        $my_prereqs->setAttribute('type', 'aicc_script');
9258
                        $my_item->appendChild($my_prereqs);
9259
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9260
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9261
                        $my_item->appendChild($my_masteryscore);
9262
9263
                        // Attach this item to the organization element or hits parent if there is one.
9264
                        if (!empty($item->parent) && 0 != $item->parent) {
9265
                            $children = $organization->childNodes;
9266
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9267
                            if ($possible_parent) {
9268
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9269
                                    $possible_parent->appendChild($my_item);
9270
                                }
9271
                            }
9272
                        } else {
9273
                            $organization->appendChild($my_item);
9274
                        }
9275
9276
                        // Get the path of the file(s) from the course directory root
9277
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9278
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9279
                        // Write the contents of the exported exercise into a (big) html file
9280
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9281
                        $scormExercise = new ScormExercise($exe, true);
9282
                        $contents = $scormExercise->export();
9283
9284
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9285
                        $res = file_put_contents($tmp_file_path, $contents);
9286
                        if (false === $res) {
9287
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9288
                        }
9289
                        $files_cleanup[] = $tmp_file_path;
9290
                        $my_xml_file_path = $my_file_path;
9291
                        $my_sub_dir = dirname($my_file_path);
9292
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9293
                        $my_xml_sub_dir = $my_sub_dir;
9294
                        // Give a <resource> child to the <resources> element.
9295
                        $my_resource = $xmldoc->createElement('resource');
9296
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9297
                        $my_resource->setAttribute('type', 'webcontent');
9298
                        $my_resource->setAttribute('href', $my_xml_file_path);
9299
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9300
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9301
                        // xml:base is the base directory to find the files declared in this resource.
9302
                        $my_resource->setAttribute('xml:base', '');
9303
                        // Give a <file> child to the <resource> element.
9304
                        $my_file = $xmldoc->createElement('file');
9305
                        $my_file->setAttribute('href', $my_xml_file_path);
9306
                        $my_resource->appendChild($my_file);
9307
9308
                        // Get included docs.
9309
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9310
9311
                        // Dependency to other files - not yet supported.
9312
                        $i = 1;
9313
                        foreach ($inc_docs as $doc_info) {
9314
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9315
                                continue;
9316
                            }
9317
                            $my_dep = $xmldoc->createElement('resource');
9318
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9319
                            $my_dep->setAttribute('identifier', $res_id);
9320
                            $my_dep->setAttribute('type', 'webcontent');
9321
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9322
                            $my_dep_file = $xmldoc->createElement('file');
9323
                            // Check type of URL.
9324
                            if ('remote' == $doc_info[1]) {
9325
                                // Remote file. Save url as is.
9326
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9327
                                $my_dep->setAttribute('xml:base', '');
9328
                            } elseif ('local' == $doc_info[1]) {
9329
                                switch ($doc_info[2]) {
9330
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9331
                                        // Save file but as local file (retrieve from URL).
9332
                                        $abs_path = api_get_path(SYS_PATH).
9333
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9334
                                        $current_dir = dirname($abs_path);
9335
                                        $current_dir = str_replace('\\', '/', $current_dir);
9336
                                        $file_path = realpath($abs_path);
9337
                                        $file_path = str_replace('\\', '/', $file_path);
9338
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9339
                                        $my_dep->setAttribute('xml:base', '');
9340
                                        if (false !== strstr($file_path, $main_path)) {
9341
                                            // The calculated real path is really inside the chamilo root path.
9342
                                            // Reduce file path to what's under the DocumentRoot.
9343
                                            $file_path = substr($file_path, strlen($root_path));
9344
                                            $zip_files_abs[] = $file_path;
9345
                                            $link_updates[$my_file_path][] = [
9346
                                                'orig' => $doc_info[0],
9347
                                                'dest' => 'document/'.$file_path,
9348
                                            ];
9349
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9350
                                            $my_dep->setAttribute('xml:base', '');
9351
                                        } elseif (empty($file_path)) {
9352
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9353
                                            $file_path = str_replace('//', '/', $file_path);
9354
                                            if (file_exists($file_path)) {
9355
                                                $file_path = substr($file_path, strlen($current_dir));
9356
                                                // We get the relative path.
9357
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9358
                                                $link_updates[$my_file_path][] = [
9359
                                                    'orig' => $doc_info[0],
9360
                                                    'dest' => 'document/'.$file_path,
9361
                                                ];
9362
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9363
                                                $my_dep->setAttribute('xml:base', '');
9364
                                            }
9365
                                        }
9366
                                        break;
9367
                                    case 'abs':
9368
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9369
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9370
                                        $current_dir = str_replace('\\', '/', $current_dir);
9371
                                        $file_path = realpath($doc_info[0]);
9372
                                        $file_path = str_replace('\\', '/', $file_path);
9373
                                        $my_dep_file->setAttribute('href', $file_path);
9374
                                        $my_dep->setAttribute('xml:base', '');
9375
9376
                                        if (false !== strstr($file_path, $main_path)) {
9377
                                            // The calculated real path is really inside the chamilo root path.
9378
                                            // Reduce file path to what's under the DocumentRoot.
9379
                                            $file_path = substr($file_path, strlen($root_path));
9380
                                            $zip_files_abs[] = $file_path;
9381
                                            $link_updates[$my_file_path][] = [
9382
                                                'orig' => $doc_info[0],
9383
                                                'dest' => $file_path,
9384
                                            ];
9385
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9386
                                            $my_dep->setAttribute('xml:base', '');
9387
                                        } elseif (empty($file_path)) {
9388
                                            $docSysPartPath = str_replace(
9389
                                                api_get_path(REL_COURSE_PATH),
9390
                                                '',
9391
                                                $doc_info[0]
9392
                                            );
9393
9394
                                            $docSysPartPathNoCourseCode = str_replace(
9395
                                                $_course['directory'].'/',
9396
                                                '',
9397
                                                $docSysPartPath
9398
                                            );
9399
9400
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9401
                                            if (file_exists($docSysPath)) {
9402
                                                $file_path = $docSysPartPathNoCourseCode;
9403
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9404
                                                $link_updates[$my_file_path][] = [
9405
                                                    'orig' => $doc_info[0],
9406
                                                    'dest' => $file_path,
9407
                                                ];
9408
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9409
                                                $my_dep->setAttribute('xml:base', '');
9410
                                            }
9411
                                        }
9412
                                        break;
9413
                                    case 'rel':
9414
                                        // Path relative to the current document. Save xml:base as current document's
9415
                                        // directory and save file in zip as subdir.file_path
9416
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9417
                                            // Relative path going up.
9418
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9419
                                            $current_dir = str_replace('\\', '/', $current_dir);
9420
                                            $file_path = realpath($current_dir.$doc_info[0]);
9421
                                            $file_path = str_replace('\\', '/', $file_path);
9422
                                            if (false !== strstr($file_path, $main_path)) {
9423
                                                // The calculated real path is really inside Chamilo's root path.
9424
                                                // Reduce file path to what's under the DocumentRoot.
9425
9426
                                                $file_path = substr($file_path, strlen($root_path));
9427
                                                $file_path_dest = $file_path;
9428
9429
                                                // File path is courses/CHAMILO/document/....
9430
                                                $info_file_path = explode('/', $file_path);
9431
                                                if ('courses' == $info_file_path[0]) {
9432
                                                    // Add character "/" in file path.
9433
                                                    $file_path_dest = 'document/'.$file_path;
9434
                                                }
9435
                                                $zip_files_abs[] = $file_path;
9436
9437
                                                $link_updates[$my_file_path][] = [
9438
                                                    'orig' => $doc_info[0],
9439
                                                    'dest' => $file_path_dest,
9440
                                                ];
9441
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9442
                                                $my_dep->setAttribute('xml:base', '');
9443
                                            }
9444
                                        } else {
9445
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9446
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9447
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9448
                                        }
9449
                                        break;
9450
                                    default:
9451
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9452
                                        $my_dep->setAttribute('xml:base', '');
9453
                                        break;
9454
                                }
9455
                            }
9456
                            $my_dep->appendChild($my_dep_file);
9457
                            $resources->appendChild($my_dep);
9458
                            $dependency = $xmldoc->createElement('dependency');
9459
                            $dependency->setAttribute('identifierref', $res_id);
9460
                            $my_resource->appendChild($dependency);
9461
                            $i++;
9462
                        }
9463
                        $resources->appendChild($my_resource);
9464
                        $zip_files[] = $my_file_path;
9465
                        break;
9466
                    default:
9467
                        // Get the path of the file(s) from the course directory root
9468
                        $my_file_path = 'non_exportable.html';
9469
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9470
                        $my_xml_file_path = $my_file_path;
9471
                        $my_sub_dir = dirname($my_file_path);
9472
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9473
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9474
                        $my_xml_sub_dir = $my_sub_dir;
9475
                        // Give a <resource> child to the <resources> element.
9476
                        $my_resource = $xmldoc->createElement('resource');
9477
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9478
                        $my_resource->setAttribute('type', 'webcontent');
9479
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9480
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9481
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9482
                        // xml:base is the base directory to find the files declared in this resource.
9483
                        $my_resource->setAttribute('xml:base', '');
9484
                        // Give a <file> child to the <resource> element.
9485
                        $my_file = $xmldoc->createElement('file');
9486
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9487
                        $my_resource->appendChild($my_file);
9488
                        $resources->appendChild($my_resource);
9489
                        break;
9490
                }
9491
            }
9492
        }
9493
        $organizations->appendChild($organization);
9494
        $root->appendChild($organizations);
9495
        $root->appendChild($resources);
9496
        $xmldoc->appendChild($root);
9497
9498
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9499
9500
        // then add the file to the zip, then destroy the file (this is done automatically).
9501
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9502
        foreach ($zip_files as $file_path) {
9503
            if (empty($file_path)) {
9504
                continue;
9505
            }
9506
9507
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9508
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9509
9510
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9511
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9512
            }
9513
9514
            $this->create_path($dest_file);
9515
            @copy($filePath, $dest_file);
9516
9517
            // Check if the file needs a link update.
9518
            if (in_array($file_path, array_keys($link_updates))) {
9519
                $string = file_get_contents($dest_file);
9520
                unlink($dest_file);
9521
                foreach ($link_updates[$file_path] as $old_new) {
9522
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9523
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9524
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9525
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9526
                    if ('flv' === substr($old_new['dest'], -3) &&
9527
                        'main/' === substr($old_new['dest'], 0, 5)
9528
                    ) {
9529
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9530
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9531
                        'video/' === substr($old_new['dest'], 0, 6)
9532
                    ) {
9533
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9534
                    }
9535
9536
                    // Fix to avoid problems with default_course_document
9537
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9538
                        $newDestination = $old_new['dest'];
9539
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9540
                            $newDestination = $old_new['replace'];
9541
                        }
9542
                    } else {
9543
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9544
                    }
9545
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9546
9547
                    // Add files inside the HTMLs
9548
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9549
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9550
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9551
                        copy(
9552
                            $sys_course_path.$new_path,
9553
                            $destinationFile
9554
                        );
9555
                    }
9556
                }
9557
                file_put_contents($dest_file, $string);
9558
            }
9559
9560
            if (file_exists($filePath) && $copyAll) {
9561
                $extension = $this->get_extension($filePath);
9562
                if (in_array($extension, ['html', 'html'])) {
9563
                    $containerOrigin = dirname($filePath);
9564
                    $containerDestination = dirname($dest_file);
9565
9566
                    $finder = new Finder();
9567
                    $finder->files()->in($containerOrigin)
9568
                        ->notName('*_DELETED_*')
9569
                        ->exclude('share_folder')
9570
                        ->exclude('chat_files')
9571
                        ->exclude('certificates')
9572
                    ;
9573
9574
                    if (is_dir($containerOrigin) &&
9575
                        is_dir($containerDestination)
9576
                    ) {
9577
                        $fs = new Filesystem();
9578
                        $fs->mirror(
9579
                            $containerOrigin,
9580
                            $containerDestination,
9581
                            $finder
9582
                        );
9583
                    }
9584
                }
9585
            }
9586
        }
9587
9588
        foreach ($zip_files_abs as $file_path) {
9589
            if (empty($file_path)) {
9590
                continue;
9591
            }
9592
9593
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9594
                continue;
9595
            }
9596
9597
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9598
            if (false !== strstr($file_path, 'upload/users')) {
9599
                $pos = strpos($file_path, 'my_files/');
9600
                if (false !== $pos) {
9601
                    $onlyDirectory = str_replace(
9602
                        'upload/users/',
9603
                        '',
9604
                        substr($file_path, $pos, strlen($file_path))
9605
                    );
9606
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9607
                }
9608
            }
9609
9610
            if (false !== strstr($file_path, 'default_course_document/')) {
9611
                $replace = str_replace('/main', '', $file_path);
9612
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9613
            }
9614
9615
            if (empty($dest_file)) {
9616
                continue;
9617
            }
9618
9619
            $this->create_path($dest_file);
9620
            copy($main_path.$file_path, $dest_file);
9621
            // Check if the file needs a link update.
9622
            if (in_array($file_path, array_keys($link_updates))) {
9623
                $string = file_get_contents($dest_file);
9624
                unlink($dest_file);
9625
                foreach ($link_updates[$file_path] as $old_new) {
9626
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9627
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9628
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9629
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9630
                    if ('flv' == substr($old_new['dest'], -3) &&
9631
                        'main/' == substr($old_new['dest'], 0, 5)
9632
                    ) {
9633
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9634
                    }
9635
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9636
                }
9637
                file_put_contents($dest_file, $string);
9638
            }
9639
        }
9640
9641
        if (is_array($links_to_create)) {
9642
            foreach ($links_to_create as $file => $link) {
9643
                $content = '<!DOCTYPE html><head>
9644
                            <meta charset="'.api_get_language_isocode().'" />
9645
                            <title>'.$link['title'].'</title>
9646
                            </head>
9647
                            <body dir="'.api_get_text_direction().'">
9648
                            <div style="text-align:center">
9649
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9650
                            </body>
9651
                            </html>';
9652
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9653
            }
9654
        }
9655
9656
        // Add non exportable message explanation.
9657
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9658
        $file_content = '<!DOCTYPE html><head>
9659
                        <meta charset="'.api_get_language_isocode().'" />
9660
                        <title>'.$lang_not_exportable.'</title>
9661
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9662
                        </head>
9663
                        <body dir="'.api_get_text_direction().'">';
9664
        $file_content .=
9665
            <<<EOD
9666
                    <style>
9667
            .error-message {
9668
                font-family: arial, verdana, helvetica, sans-serif;
9669
                border-width: 1px;
9670
                border-style: solid;
9671
                left: 50%;
9672
                margin: 10px auto;
9673
                min-height: 30px;
9674
                padding: 5px;
9675
                right: 50%;
9676
                width: 500px;
9677
                background-color: #FFD1D1;
9678
                border-color: #FF0000;
9679
                color: #000;
9680
            }
9681
        </style>
9682
    <body>
9683
        <div class="error-message">
9684
            $lang_not_exportable
9685
        </div>
9686
    </body>
9687
</html>
9688
EOD;
9689
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9690
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9691
        }
9692
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9693
9694
        // Add the extra files that go along with a SCORM package.
9695
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9696
9697
        $fs = new Filesystem();
9698
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9699
9700
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9701
        $manifest = @$xmldoc->saveXML();
9702
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9703
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9704
        $zip_folder->add(
9705
            $archivePath.'/'.$temp_dir_short,
9706
            PCLZIP_OPT_REMOVE_PATH,
9707
            $archivePath.'/'.$temp_dir_short.'/'
9708
        );
9709
9710
        // Clean possible temporary files.
9711
        foreach ($files_cleanup as $file) {
9712
            $res = unlink($file);
9713
            if (false === $res) {
9714
                error_log(
9715
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9716
                    0
9717
                );
9718
            }
9719
        }
9720
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9721
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9722
    }
9723
9724
    /**
9725
     * @param int $lp_id
9726
     *
9727
     * @return bool
9728
     */
9729
    public function scorm_export_to_pdf($lp_id)
9730
    {
9731
        $lp_id = (int) $lp_id;
9732
        $files_to_export = [];
9733
9734
        $sessionId = api_get_session_id();
9735
        $course_data = api_get_course_info($this->cc);
9736
9737
        if (!empty($course_data)) {
9738
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9739
            $list = self::get_flat_ordered_items_list($lp_id);
9740
            if (!empty($list)) {
9741
                foreach ($list as $item_id) {
9742
                    $item = $this->items[$item_id];
9743
                    switch ($item->type) {
9744
                        case 'document':
9745
                            // Getting documents from a LP with chamilo documents
9746
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9747
                            // Try loading document from the base course.
9748
                            if (empty($file_data) && !empty($sessionId)) {
9749
                                $file_data = DocumentManager::get_document_data_by_id(
9750
                                    $item->path,
9751
                                    $this->cc,
9752
                                    false,
9753
                                    0
9754
                                );
9755
                            }
9756
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9757
                            if (file_exists($file_path)) {
9758
                                $files_to_export[] = [
9759
                                    'title' => $item->get_title(),
9760
                                    'path' => $file_path,
9761
                                ];
9762
                            }
9763
                            break;
9764
                        case 'asset': //commes from a scorm package generated by chamilo
9765
                        case 'sco':
9766
                            $file_path = $scorm_path.'/'.$item->path;
9767
                            if (file_exists($file_path)) {
9768
                                $files_to_export[] = [
9769
                                    'title' => $item->get_title(),
9770
                                    'path' => $file_path,
9771
                                ];
9772
                            }
9773
                            break;
9774
                        case 'dir':
9775
                            $files_to_export[] = [
9776
                                'title' => $item->get_title(),
9777
                                'path' => null,
9778
                            ];
9779
                            break;
9780
                    }
9781
                }
9782
            }
9783
9784
            $pdf = new PDF();
9785
            $result = $pdf->html_to_pdf(
9786
                $files_to_export,
9787
                $this->name,
9788
                $this->cc,
9789
                true,
9790
                true,
9791
                true,
9792
                $this->get_name()
9793
            );
9794
9795
            return $result;
9796
        }
9797
9798
        return false;
9799
    }
9800
9801
    /**
9802
     * Temp function to be moved in main_api or the best place around for this.
9803
     * Creates a file path if it doesn't exist.
9804
     *
9805
     * @param string $path
9806
     */
9807
    public function create_path($path)
9808
    {
9809
        $path_bits = explode('/', dirname($path));
9810
9811
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9812
        $path_built = IS_WINDOWS_OS ? '' : '/';
9813
        foreach ($path_bits as $bit) {
9814
            if (!empty($bit)) {
9815
                $new_path = $path_built.$bit;
9816
                if (is_dir($new_path)) {
9817
                    $path_built = $new_path.'/';
9818
                } else {
9819
                    mkdir($new_path, api_get_permissions_for_new_directories());
9820
                    $path_built = $new_path.'/';
9821
                }
9822
            }
9823
        }
9824
    }
9825
9826
    /**
9827
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9828
     *
9829
     * @return bool The results of the unlink function, or false if there was no image to start with
9830
     */
9831
    public function delete_lp_image()
9832
    {
9833
        $img = $this->get_preview_image();
9834
        if ('' != $img) {
9835
            $del_file = $this->get_preview_image_path(null, 'sys');
9836
            if (isset($del_file) && file_exists($del_file)) {
9837
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9838
                if (file_exists($del_file_2)) {
9839
                    unlink($del_file_2);
9840
                }
9841
                $this->set_preview_image('');
9842
9843
                return @unlink($del_file);
9844
            }
9845
        }
9846
9847
        return false;
9848
    }
9849
9850
    /**
9851
     * Uploads an author image to the upload/learning_path/images directory.
9852
     *
9853
     * @param array    The image array, coming from the $_FILES superglobal
9854
     *
9855
     * @return bool True on success, false on error
9856
     */
9857
    public function upload_image($image_array)
9858
    {
9859
        if (!empty($image_array['name'])) {
9860
            $upload_ok = process_uploaded_file($image_array);
9861
            $has_attachment = true;
9862
        }
9863
9864
        if ($upload_ok && $has_attachment) {
9865
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9866
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9867
            $updir = $sys_course_path.$courseDir;
9868
            // Try to add an extension to the file if it hasn't one.
9869
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9870
9871
            if (filter_extension($new_file_name)) {
9872
                $file_extension = explode('.', $image_array['name']);
9873
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9874
                $filename = uniqid('');
9875
                $new_file_name = $filename.'.'.$file_extension;
9876
                $new_path = $updir.'/'.$new_file_name;
9877
9878
                // Resize the image.
9879
                $temp = new Image($image_array['tmp_name']);
9880
                $temp->resize(104);
9881
                $result = $temp->send_image($new_path);
9882
9883
                // Storing the image filename.
9884
                if ($result) {
9885
                    $this->set_preview_image($new_file_name);
9886
9887
                    //Resize to 64px to use on course homepage
9888
                    $temp->resize(64);
9889
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9890
9891
                    return true;
9892
                }
9893
            }
9894
        }
9895
9896
        return false;
9897
    }
9898
9899
    /**
9900
     * @param int    $lp_id
9901
     * @param string $status
9902
     */
9903
    public function set_autolaunch($lp_id, $status)
9904
    {
9905
        $course_id = api_get_course_int_id();
9906
        $lp_id = (int) $lp_id;
9907
        $status = (int) $status;
9908
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9909
9910
        // Setting everything to autolaunch = 0
9911
        $attributes['autolaunch'] = 0;
9912
        $where = [
9913
            'session_id = ? AND c_id = ? ' => [
9914
                api_get_session_id(),
9915
                $course_id,
9916
            ],
9917
        ];
9918
        Database::update($lp_table, $attributes, $where);
9919
        if (1 == $status) {
9920
            //Setting my lp_id to autolaunch = 1
9921
            $attributes['autolaunch'] = 1;
9922
            $where = [
9923
                'iid = ? AND session_id = ? AND c_id = ?' => [
9924
                    $lp_id,
9925
                    api_get_session_id(),
9926
                    $course_id,
9927
                ],
9928
            ];
9929
            Database::update($lp_table, $attributes, $where);
9930
        }
9931
    }
9932
9933
    /**
9934
     * Gets previous_item_id for the next element of the lp_item table.
9935
     *
9936
     * @author Isaac flores paz
9937
     *
9938
     * @return int Previous item ID
9939
     */
9940
    public function select_previous_item_id()
9941
    {
9942
        $course_id = api_get_course_int_id();
9943
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9944
9945
        // Get the max order of the items
9946
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9947
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9948
        $rs_max_order = Database::query($sql);
9949
        $row_max_order = Database::fetch_object($rs_max_order);
9950
        $max_order = $row_max_order->display_order;
9951
        // Get the previous item ID
9952
        $sql = "SELECT iid as previous FROM $table_lp_item
9953
                WHERE
9954
                    c_id = $course_id AND
9955
                    lp_id = ".$this->lp_id." AND
9956
                    display_order = '$max_order' ";
9957
        $rs_max = Database::query($sql);
9958
        $row_max = Database::fetch_object($rs_max);
9959
9960
        // Return the previous item ID
9961
        return $row_max->previous;
9962
    }
9963
9964
    /**
9965
     * Copies an LP.
9966
     */
9967
    public function copy()
9968
    {
9969
        // Course builder
9970
        $cb = new CourseBuilder();
9971
9972
        //Setting tools that will be copied
9973
        $cb->set_tools_to_build(['learnpaths']);
9974
9975
        //Setting elements that will be copied
9976
        $cb->set_tools_specific_id_list(
9977
            ['learnpaths' => [$this->lp_id]]
9978
        );
9979
9980
        $course = $cb->build();
9981
9982
        //Course restorer
9983
        $course_restorer = new CourseRestorer($course);
9984
        $course_restorer->set_add_text_in_items(true);
9985
        $course_restorer->set_tool_copy_settings(
9986
            ['learnpaths' => ['reset_dates' => true]]
9987
        );
9988
        $course_restorer->restore(
9989
            api_get_course_id(),
9990
            api_get_session_id(),
9991
            false,
9992
            false
9993
        );
9994
    }
9995
9996
    /**
9997
     * Verify document size.
9998
     *
9999
     * @param string $s
10000
     *
10001
     * @return bool
10002
     */
10003
    public static function verify_document_size($s)
10004
    {
10005
        $post_max = ini_get('post_max_size');
10006
        if ('M' == substr($post_max, -1, 1)) {
10007
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
10008
        } elseif ('G' == substr($post_max, -1, 1)) {
10009
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
10010
        }
10011
        $upl_max = ini_get('upload_max_filesize');
10012
        if ('M' == substr($upl_max, -1, 1)) {
10013
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
10014
        } elseif ('G' == substr($upl_max, -1, 1)) {
10015
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
10016
        }
10017
10018
        $repo = Container::getDocumentRepository();
10019
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
10020
10021
        $course_max_space = DocumentManager::get_course_quota();
10022
        $total_size = filesize($s) + $documents_total_space;
10023
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
10024
            return true;
10025
        }
10026
10027
        return false;
10028
    }
10029
10030
    /**
10031
     * Clear LP prerequisites.
10032
     */
10033
    public function clear_prerequisites()
10034
    {
10035
        $course_id = $this->get_course_int_id();
10036
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10037
        $lp_id = $this->get_id();
10038
        // Cleaning prerequisites
10039
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
10040
                WHERE c_id = $course_id AND lp_id = $lp_id";
10041
        Database::query($sql);
10042
10043
        // Cleaning mastery score for exercises
10044
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
10045
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
10046
        Database::query($sql);
10047
    }
10048
10049
    public function set_previous_step_as_prerequisite_for_all_items()
10050
    {
10051
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10052
        $course_id = $this->get_course_int_id();
10053
        $lp_id = $this->get_id();
10054
10055
        if (!empty($this->items)) {
10056
            $previous_item_id = null;
10057
            $previous_item_max = 0;
10058
            $previous_item_type = null;
10059
            $last_item_not_dir = null;
10060
            $last_item_not_dir_type = null;
10061
            $last_item_not_dir_max = null;
10062
10063
            foreach ($this->ordered_items as $itemId) {
10064
                $item = $this->getItem($itemId);
10065
                // if there was a previous item... (otherwise jump to set it)
10066
                if (!empty($previous_item_id)) {
10067
                    $current_item_id = $item->get_id(); //save current id
10068
                    if ('dir' != $item->get_type()) {
10069
                        // Current item is not a folder, so it qualifies to get a prerequisites
10070
                        if ('quiz' == $last_item_not_dir_type) {
10071
                            // if previous is quiz, mark its max score as default score to be achieved
10072
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
10073
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
10074
                            Database::query($sql);
10075
                        }
10076
                        // now simply update the prerequisite to set it to the last non-chapter item
10077
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
10078
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
10079
                        Database::query($sql);
10080
                        // record item as 'non-chapter' reference
10081
                        $last_item_not_dir = $item->get_id();
10082
                        $last_item_not_dir_type = $item->get_type();
10083
                        $last_item_not_dir_max = $item->get_max();
10084
                    }
10085
                } else {
10086
                    if ('dir' != $item->get_type()) {
10087
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
10088
                        $last_item_not_dir = $item->get_id();
10089
                        $last_item_not_dir_type = $item->get_type();
10090
                        $last_item_not_dir_max = $item->get_max();
10091
                    }
10092
                }
10093
                // Saving the item as "previous item" for the next loop
10094
                $previous_item_id = $item->get_id();
10095
                $previous_item_max = $item->get_max();
10096
                $previous_item_type = $item->get_type();
10097
            }
10098
        }
10099
    }
10100
10101
    /**
10102
     * @param array $params
10103
     *
10104
     * @throws \Doctrine\ORM\OptimisticLockException
10105
     *
10106
     * @return int
10107
     */
10108
    public static function createCategory($params)
10109
    {
10110
        $item = new CLpCategory();
10111
        $item->setName($params['name']);
10112
        $item->setCId($params['c_id']);
10113
10114
        $repo = Container::getLpCategoryRepository();
10115
        $em = $repo->getEntityManager();
10116
        $em->persist($item);
10117
        $courseEntity = api_get_course_entity(api_get_course_int_id());
10118
10119
        $repo->addResourceToCourse(
10120
            $item,
10121
            ResourceLink::VISIBILITY_PUBLISHED,
10122
            api_get_user_entity(api_get_user_id()),
10123
            $courseEntity,
10124
            api_get_session_entity(),
10125
            api_get_group_entity()
10126
        );
10127
10128
        $em->flush();
10129
10130
        /*api_item_property_update(
10131
            api_get_course_info(),
10132
            TOOL_LEARNPATH_CATEGORY,
10133
            $item->getId(),
10134
            'visible',
10135
            api_get_user_id()
10136
        );*/
10137
10138
        return $item->getId();
10139
    }
10140
10141
    /**
10142
     * @param array $params
10143
     *
10144
     * @throws \Doctrine\ORM\ORMException
10145
     * @throws \Doctrine\ORM\OptimisticLockException
10146
     * @throws \Doctrine\ORM\TransactionRequiredException
10147
     */
10148
    public static function updateCategory($params)
10149
    {
10150
        $em = Database::getManager();
10151
        /** @var CLpCategory $item */
10152
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10153
        if ($item) {
10154
            $item->setName($params['name']);
10155
            $em->merge($item);
10156
            $em->flush();
10157
        }
10158
    }
10159
10160
    /**
10161
     * @param int $id
10162
     */
10163
    public static function moveUpCategory($id)
10164
    {
10165
        $id = (int) $id;
10166
        $em = Database::getManager();
10167
        /** @var CLpCategory $item */
10168
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10169
        if ($item) {
10170
            $position = $item->getPosition() - 1;
10171
            $item->setPosition($position);
10172
            $em->persist($item);
10173
            $em->flush();
10174
        }
10175
    }
10176
10177
    /**
10178
     * @param int $id
10179
     */
10180
    public static function moveDownCategory($id)
10181
    {
10182
        $id = (int) $id;
10183
        $em = Database::getManager();
10184
        /** @var CLpCategory $item */
10185
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10186
        if ($item) {
10187
            $position = $item->getPosition() + 1;
10188
            $item->setPosition($position);
10189
            $em->persist($item);
10190
            $em->flush();
10191
        }
10192
    }
10193
10194
    /**
10195
     * @param int $courseId
10196
     *
10197
     * @throws \Doctrine\ORM\Query\QueryException
10198
     *
10199
     * @return int|mixed
10200
     */
10201
    public static function getCountCategories($courseId)
10202
    {
10203
        if (empty($courseId)) {
10204
            return 0;
10205
        }
10206
        $em = Database::getManager();
10207
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10208
        $query->setParameter('id', $courseId);
10209
10210
        return $query->getSingleScalarResult();
10211
    }
10212
10213
    /**
10214
     * @param int $courseId
10215
     *
10216
     * @return mixed
10217
     */
10218
    public static function getCategories($courseId)
10219
    {
10220
        $em = Database::getManager();
10221
10222
        // Using doctrine extensions
10223
        /** @var SortableRepository $repo */
10224
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10225
10226
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
10227
    }
10228
10229
    /**
10230
     * @param int $id
10231
     *
10232
     * @return CLpCategory
10233
     */
10234
    public static function getCategory($id)
10235
    {
10236
        $id = (int) $id;
10237
        $em = Database::getManager();
10238
10239
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
10240
    }
10241
10242
    /**
10243
     * @param int $courseId
10244
     *
10245
     * @return array
10246
     */
10247
    public static function getCategoryByCourse($courseId)
10248
    {
10249
        $em = Database::getManager();
10250
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10251
            ['cId' => $courseId]
10252
        );
10253
10254
        return $items;
10255
    }
10256
10257
    /**
10258
     * @param int $id
10259
     *
10260
     * @return mixed
10261
     */
10262
    public static function deleteCategory($id)
10263
    {
10264
        $em = Database::getManager();
10265
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10266
        if ($item) {
10267
            $courseId = $item->getCId();
10268
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
10269
            $query->setParameter('id', $courseId);
10270
            $query->setParameter('catId', $item->getId());
10271
            $lps = $query->getResult();
10272
10273
            // Setting category = 0.
10274
            if ($lps) {
10275
                foreach ($lps as $lpItem) {
10276
                    $lpItem->setCategoryId(0);
10277
                }
10278
            }
10279
10280
            // Removing category.
10281
            $em->remove($item);
10282
            $em->flush();
10283
10284
            $courseInfo = api_get_course_info_by_id($courseId);
10285
            $sessionId = api_get_session_id();
10286
10287
            // Delete link tool
10288
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
10289
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
10290
            // Delete tools
10291
            $sql = "DELETE FROM $tbl_tool
10292
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
10293
            Database::query($sql);*/
10294
10295
            return true;
10296
        }
10297
10298
        return false;
10299
    }
10300
10301
    /**
10302
     * @param int  $courseId
10303
     * @param bool $addSelectOption
10304
     *
10305
     * @return mixed
10306
     */
10307
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10308
    {
10309
        $items = self::getCategoryByCourse($courseId);
10310
        $cats = [];
10311
        if ($addSelectOption) {
10312
            $cats = [get_lang('Select a category')];
10313
        }
10314
10315
        if (!empty($items)) {
10316
            foreach ($items as $cat) {
10317
                $cats[$cat->getId()] = $cat->getName();
10318
            }
10319
        }
10320
10321
        return $cats;
10322
    }
10323
10324
    /**
10325
     * @param string $courseCode
10326
     * @param int    $lpId
10327
     * @param int    $user_id
10328
     *
10329
     * @return learnpath
10330
     */
10331
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10332
    {
10333
        $debug = 0;
10334
        $learnPath = null;
10335
        $lpObject = Session::read('lpobject');
10336
        if (null !== $lpObject) {
10337
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10338
            if ($debug) {
10339
                error_log('getLpFromSession: unserialize');
10340
                error_log('------getLpFromSession------');
10341
                error_log('------unserialize------');
10342
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10343
                error_log("api_get_sessionid: ".api_get_session_id());
10344
            }
10345
        }
10346
10347
        if (!is_object($learnPath)) {
10348
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10349
            if ($debug) {
10350
                error_log('------getLpFromSession------');
10351
                error_log('getLpFromSession: create new learnpath');
10352
                error_log("create new LP with $courseCode - $lpId - $user_id");
10353
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10354
                error_log("api_get_sessionid: ".api_get_session_id());
10355
            }
10356
        }
10357
10358
        return $learnPath;
10359
    }
10360
10361
    /**
10362
     * @param int $itemId
10363
     *
10364
     * @return learnpathItem|false
10365
     */
10366
    public function getItem($itemId)
10367
    {
10368
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10369
            return $this->items[$itemId];
10370
        }
10371
10372
        return false;
10373
    }
10374
10375
    /**
10376
     * @return int
10377
     */
10378
    public function getCurrentAttempt()
10379
    {
10380
        $attempt = $this->getItem($this->get_current_item_id());
10381
        if ($attempt) {
10382
            return $attempt->get_attempt_id();
10383
        }
10384
10385
        return 0;
10386
    }
10387
10388
    /**
10389
     * @return int
10390
     */
10391
    public function getCategoryId()
10392
    {
10393
        return (int) $this->categoryId;
10394
    }
10395
10396
    /**
10397
     * @param int $categoryId
10398
     *
10399
     * @return bool
10400
     */
10401
    public function setCategoryId($categoryId)
10402
    {
10403
        $this->categoryId = (int) $categoryId;
10404
        $table = Database::get_course_table(TABLE_LP_MAIN);
10405
        $lp_id = $this->get_id();
10406
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10407
                WHERE iid = $lp_id";
10408
        Database::query($sql);
10409
10410
        return true;
10411
    }
10412
10413
    /**
10414
     * Get whether this is a learning path with the possibility to subscribe
10415
     * users or not.
10416
     *
10417
     * @return int
10418
     */
10419
    public function getSubscribeUsers()
10420
    {
10421
        return $this->subscribeUsers;
10422
    }
10423
10424
    /**
10425
     * Set whether this is a learning path with the possibility to subscribe
10426
     * users or not.
10427
     *
10428
     * @param int $value (0 = false, 1 = true)
10429
     *
10430
     * @return bool
10431
     */
10432
    public function setSubscribeUsers($value)
10433
    {
10434
        $this->subscribeUsers = (int) $value;
10435
        $table = Database::get_course_table(TABLE_LP_MAIN);
10436
        $lp_id = $this->get_id();
10437
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10438
                WHERE iid = $lp_id";
10439
        Database::query($sql);
10440
10441
        return true;
10442
    }
10443
10444
    /**
10445
     * Calculate the count of stars for a user in this LP
10446
     * This calculation is based on the following rules:
10447
     * - the student gets one star when he gets to 50% of the learning path
10448
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10449
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10450
     * - the student gets the final star when the score for the *last* test is >= 80%.
10451
     *
10452
     * @param int $sessionId Optional. The session ID
10453
     *
10454
     * @return int The count of stars
10455
     */
10456
    public function getCalculateStars($sessionId = 0)
10457
    {
10458
        $stars = 0;
10459
        $progress = self::getProgress(
10460
            $this->lp_id,
10461
            $this->user_id,
10462
            $this->course_int_id,
10463
            $sessionId
10464
        );
10465
10466
        if ($progress >= 50) {
10467
            $stars++;
10468
        }
10469
10470
        // Calculate stars chapters evaluation
10471
        $exercisesItems = $this->getExercisesItems();
10472
10473
        if (!empty($exercisesItems)) {
10474
            $totalResult = 0;
10475
10476
            foreach ($exercisesItems as $exerciseItem) {
10477
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10478
                    $this->user_id,
10479
                    $exerciseItem->path,
10480
                    $this->course_int_id,
10481
                    $sessionId,
10482
                    $this->lp_id,
10483
                    $exerciseItem->db_id
10484
                );
10485
10486
                $exerciseResultInfo = end($exerciseResultInfo);
10487
10488
                if (!$exerciseResultInfo) {
10489
                    continue;
10490
                }
10491
10492
                if (!empty($exerciseResultInfo['max_score'])) {
10493
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10494
                } else {
10495
                    $exerciseResult = 0;
10496
                }
10497
                $totalResult += $exerciseResult;
10498
            }
10499
10500
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10501
10502
            if ($totalExerciseAverage >= 50) {
10503
                $stars++;
10504
            }
10505
10506
            if ($totalExerciseAverage >= 80) {
10507
                $stars++;
10508
            }
10509
        }
10510
10511
        // Calculate star for final evaluation
10512
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10513
10514
        if (!empty($finalEvaluationItem)) {
10515
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10516
                $this->user_id,
10517
                $finalEvaluationItem->path,
10518
                $this->course_int_id,
10519
                $sessionId,
10520
                $this->lp_id,
10521
                $finalEvaluationItem->db_id
10522
            );
10523
10524
            $evaluationResultInfo = end($evaluationResultInfo);
10525
10526
            if ($evaluationResultInfo) {
10527
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10528
10529
                if ($evaluationResult >= 80) {
10530
                    $stars++;
10531
                }
10532
            }
10533
        }
10534
10535
        return $stars;
10536
    }
10537
10538
    /**
10539
     * Get the items of exercise type.
10540
     *
10541
     * @return array The items. Otherwise return false
10542
     */
10543
    public function getExercisesItems()
10544
    {
10545
        $exercises = [];
10546
        foreach ($this->items as $item) {
10547
            if ('quiz' != $item->type) {
10548
                continue;
10549
            }
10550
            $exercises[] = $item;
10551
        }
10552
10553
        array_pop($exercises);
10554
10555
        return $exercises;
10556
    }
10557
10558
    /**
10559
     * Get the item of exercise type (evaluation type).
10560
     *
10561
     * @return array The final evaluation. Otherwise return false
10562
     */
10563
    public function getFinalEvaluationItem()
10564
    {
10565
        $exercises = [];
10566
        foreach ($this->items as $item) {
10567
            if (TOOL_QUIZ !== $item->type) {
10568
                continue;
10569
            }
10570
10571
            $exercises[] = $item;
10572
        }
10573
10574
        return array_pop($exercises);
10575
    }
10576
10577
    /**
10578
     * Calculate the total points achieved for the current user in this learning path.
10579
     *
10580
     * @param int $sessionId Optional. The session Id
10581
     *
10582
     * @return int
10583
     */
10584
    public function getCalculateScore($sessionId = 0)
10585
    {
10586
        // Calculate stars chapters evaluation
10587
        $exercisesItems = $this->getExercisesItems();
10588
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10589
        $totalExercisesResult = 0;
10590
        $totalEvaluationResult = 0;
10591
10592
        if (false !== $exercisesItems) {
10593
            foreach ($exercisesItems as $exerciseItem) {
10594
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10595
                    $this->user_id,
10596
                    $exerciseItem->path,
10597
                    $this->course_int_id,
10598
                    $sessionId,
10599
                    $this->lp_id,
10600
                    $exerciseItem->db_id
10601
                );
10602
10603
                $exerciseResultInfo = end($exerciseResultInfo);
10604
10605
                if (!$exerciseResultInfo) {
10606
                    continue;
10607
                }
10608
10609
                $totalExercisesResult += $exerciseResultInfo['score'];
10610
            }
10611
        }
10612
10613
        if (!empty($finalEvaluationItem)) {
10614
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10615
                $this->user_id,
10616
                $finalEvaluationItem->path,
10617
                $this->course_int_id,
10618
                $sessionId,
10619
                $this->lp_id,
10620
                $finalEvaluationItem->db_id
10621
            );
10622
10623
            $evaluationResultInfo = end($evaluationResultInfo);
10624
10625
            if ($evaluationResultInfo) {
10626
                $totalEvaluationResult += $evaluationResultInfo['score'];
10627
            }
10628
        }
10629
10630
        return $totalExercisesResult + $totalEvaluationResult;
10631
    }
10632
10633
    /**
10634
     * Check if URL is not allowed to be show in a iframe.
10635
     *
10636
     * @param string $src
10637
     *
10638
     * @return string
10639
     */
10640
    public function fixBlockedLinks($src)
10641
    {
10642
        $urlInfo = parse_url($src);
10643
10644
        $platformProtocol = 'https';
10645
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10646
            $platformProtocol = 'http';
10647
        }
10648
10649
        $protocolFixApplied = false;
10650
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10651
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10652
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10653
10654
        if ($platformProtocol != $scheme) {
10655
            Session::write('x_frame_source', $src);
10656
            $src = 'blank.php?error=x_frames_options';
10657
            $protocolFixApplied = true;
10658
        }
10659
10660
        if (false == $protocolFixApplied) {
10661
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10662
                // Check X-Frame-Options
10663
                $ch = curl_init();
10664
                $options = [
10665
                    CURLOPT_URL => $src,
10666
                    CURLOPT_RETURNTRANSFER => true,
10667
                    CURLOPT_HEADER => true,
10668
                    CURLOPT_FOLLOWLOCATION => true,
10669
                    CURLOPT_ENCODING => "",
10670
                    CURLOPT_AUTOREFERER => true,
10671
                    CURLOPT_CONNECTTIMEOUT => 120,
10672
                    CURLOPT_TIMEOUT => 120,
10673
                    CURLOPT_MAXREDIRS => 10,
10674
                ];
10675
10676
                $proxySettings = api_get_configuration_value('proxy_settings');
10677
                if (!empty($proxySettings) &&
10678
                    isset($proxySettings['curl_setopt_array'])
10679
                ) {
10680
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10681
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10682
                }
10683
10684
                curl_setopt_array($ch, $options);
10685
                $response = curl_exec($ch);
10686
                $httpCode = curl_getinfo($ch);
10687
                $headers = substr($response, 0, $httpCode['header_size']);
10688
10689
                $error = false;
10690
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10691
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10692
                ) {
10693
                    $error = true;
10694
                }
10695
10696
                if ($error) {
10697
                    Session::write('x_frame_source', $src);
10698
                    $src = 'blank.php?error=x_frames_options';
10699
                }
10700
            }
10701
        }
10702
10703
        return $src;
10704
    }
10705
10706
    /**
10707
     * Check if this LP has a created forum in the basis course.
10708
     *
10709
     * @return bool
10710
     */
10711
    public function lpHasForum()
10712
    {
10713
        $forumTable = Database::get_course_table(TABLE_FORUM);
10714
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10715
10716
        $fakeFrom = "
10717
            $forumTable f
10718
            INNER JOIN $itemProperty ip
10719
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10720
        ";
10721
10722
        $resultData = Database::select(
10723
            'COUNT(f.iid) AS qty',
10724
            $fakeFrom,
10725
            [
10726
                'where' => [
10727
                    'ip.visibility != ? AND ' => 2,
10728
                    'ip.tool = ? AND ' => TOOL_FORUM,
10729
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10730
                    'f.lp_id = ?' => intval($this->lp_id),
10731
                ],
10732
            ],
10733
            'first'
10734
        );
10735
10736
        return $resultData['qty'] > 0;
10737
    }
10738
10739
    /**
10740
     * Get the forum for this learning path.
10741
     *
10742
     * @param int $sessionId
10743
     *
10744
     * @return array
10745
     */
10746
    public function getForum($sessionId = 0)
10747
    {
10748
        $repo = Container::getForumRepository();
10749
10750
        $course = api_get_course_entity();
10751
        $session = api_get_session_entity($sessionId);
10752
        $qb = $repo->getResourcesByCourse($course, $session);
10753
10754
        return $qb->getQuery()->getResult();
10755
    }
10756
10757
    /**
10758
     * Create a forum for this learning path.
10759
     *
10760
     * @param int $forumCategoryId
10761
     *
10762
     * @return int The forum ID if was created. Otherwise return false
10763
     */
10764
    public function createForum($forumCategoryId)
10765
    {
10766
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10767
10768
        return store_forum(
10769
            [
10770
                'lp_id' => $this->lp_id,
10771
                'forum_title' => $this->name,
10772
                'forum_comment' => null,
10773
                'forum_category' => (int) $forumCategoryId,
10774
                'students_can_edit_group' => ['students_can_edit' => 0],
10775
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10776
                'default_view_type_group' => ['default_view_type' => 'flat'],
10777
                'group_forum' => 0,
10778
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10779
            ],
10780
            [],
10781
            true
10782
        );
10783
    }
10784
10785
    /**
10786
     * Get the LP Final Item form.
10787
     *
10788
     * @throws Exception
10789
     * @throws HTML_QuickForm_Error
10790
     *
10791
     * @return string
10792
     */
10793
    public function getFinalItemForm()
10794
    {
10795
        $finalItem = $this->getFinalItem();
10796
        $title = '';
10797
10798
        if ($finalItem) {
10799
            $title = $finalItem->get_title();
10800
            $buttonText = get_lang('Save');
10801
            $content = $this->getSavedFinalItem();
10802
        } else {
10803
            $buttonText = get_lang('Add this document to the course');
10804
            $content = $this->getFinalItemTemplate();
10805
        }
10806
10807
        $editorConfig = [
10808
            'ToolbarSet' => 'LearningPathDocuments',
10809
            'Width' => '100%',
10810
            'Height' => '500',
10811
            'FullPage' => true,
10812
//            'CreateDocumentDir' => $relative_prefix,
10813
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10814
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10815
        ];
10816
10817
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10818
            'type' => 'document',
10819
            'lp_id' => $this->lp_id,
10820
        ]);
10821
10822
        $form = new FormValidator('final_item', 'POST', $url);
10823
        $form->addText('title', get_lang('Title'));
10824
        $form->addButtonSave($buttonText);
10825
        $form->addHtml(
10826
            Display::return_message(
10827
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10828
                'normal',
10829
                false
10830
            )
10831
        );
10832
10833
        $renderer = $form->defaultRenderer();
10834
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10835
10836
        $form->addHtmlEditor(
10837
            'content_lp_certificate',
10838
            null,
10839
            true,
10840
            false,
10841
            $editorConfig,
10842
            true
10843
        );
10844
        $form->addHidden('action', 'add_final_item');
10845
        $form->addHidden('path', Session::read('pathItem'));
10846
        $form->addHidden('previous', $this->get_last());
10847
        $form->setDefaults(
10848
            ['title' => $title, 'content_lp_certificate' => $content]
10849
        );
10850
10851
        if ($form->validate()) {
10852
            $values = $form->exportValues();
10853
            $lastItemId = $this->getLastInFirstLevel();
10854
10855
            if (!$finalItem) {
10856
                $documentId = $this->create_document(
10857
                    $this->course_info,
10858
                    $values['content_lp_certificate'],
10859
                    $values['title']
10860
                );
10861
                $this->add_item(
10862
                    0,
10863
                    $lastItemId,
10864
                    'final_item',
10865
                    $documentId,
10866
                    $values['title'],
10867
                    ''
10868
                );
10869
10870
                Display::addFlash(
10871
                    Display::return_message(get_lang('Added'))
10872
                );
10873
            } else {
10874
                $this->edit_document($this->course_info);
10875
            }
10876
        }
10877
10878
        return $form->returnForm();
10879
    }
10880
10881
    /**
10882
     * Check if the current lp item is first, both, last or none from lp list.
10883
     *
10884
     * @param int $currentItemId
10885
     *
10886
     * @return string
10887
     */
10888
    public function isFirstOrLastItem($currentItemId)
10889
    {
10890
        $lpItemId = [];
10891
        $typeListNotToVerify = self::getChapterTypes();
10892
10893
        // Using get_toc() function instead $this->items because returns the correct order of the items
10894
        foreach ($this->get_toc() as $item) {
10895
            if (!in_array($item['type'], $typeListNotToVerify)) {
10896
                $lpItemId[] = $item['id'];
10897
            }
10898
        }
10899
10900
        $lastLpItemIndex = count($lpItemId) - 1;
10901
        $position = array_search($currentItemId, $lpItemId);
10902
10903
        switch ($position) {
10904
            case 0:
10905
                if (!$lastLpItemIndex) {
10906
                    $answer = 'both';
10907
                    break;
10908
                }
10909
10910
                $answer = 'first';
10911
                break;
10912
            case $lastLpItemIndex:
10913
                $answer = 'last';
10914
                break;
10915
            default:
10916
                $answer = 'none';
10917
        }
10918
10919
        return $answer;
10920
    }
10921
10922
    /**
10923
     * Get whether this is a learning path with the accumulated SCORM time or not.
10924
     *
10925
     * @return int
10926
     */
10927
    public function getAccumulateScormTime()
10928
    {
10929
        return $this->accumulateScormTime;
10930
    }
10931
10932
    /**
10933
     * Set whether this is a learning path with the accumulated SCORM time or not.
10934
     *
10935
     * @param int $value (0 = false, 1 = true)
10936
     *
10937
     * @return bool Always returns true
10938
     */
10939
    public function setAccumulateScormTime($value)
10940
    {
10941
        $this->accumulateScormTime = (int) $value;
10942
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10943
        $lp_id = $this->get_id();
10944
        $sql = "UPDATE $lp_table
10945
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
10946
                WHERE iid = $lp_id";
10947
        Database::query($sql);
10948
10949
        return true;
10950
    }
10951
10952
    /**
10953
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10954
     * the new learning path tool.
10955
     *
10956
     * The function is a big switch on tool type.
10957
     * In each case, we query the corresponding table for information and build the link
10958
     * with that information.
10959
     *
10960
     * @author Yannick Warnier <[email protected]> - rebranding based on
10961
     * previous work (display_addedresource_link_in_learnpath())
10962
     *
10963
     * @param int $course_id      Course code
10964
     * @param int $learningPathId The learning path ID (in lp table)
10965
     * @param int $id_in_path     the unique index in the items table
10966
     * @param int $lpViewId
10967
     *
10968
     * @return string
10969
     */
10970
    public static function rl_get_resource_link_for_learnpath(
10971
        $course_id,
10972
        $learningPathId,
10973
        $id_in_path,
10974
        $lpViewId
10975
    ) {
10976
        $session_id = api_get_session_id();
10977
        $course_info = api_get_course_info_by_id($course_id);
10978
10979
        $learningPathId = (int) $learningPathId;
10980
        $id_in_path = (int) $id_in_path;
10981
        $lpViewId = (int) $lpViewId;
10982
10983
        $em = Database::getManager();
10984
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
10985
10986
        /** @var CLpItem $rowItem */
10987
        $rowItem = $lpItemRepo->findOneBy([
10988
            'cId' => $course_id,
10989
            'lpId' => $learningPathId,
10990
            'iid' => $id_in_path,
10991
        ]);
10992
10993
        if (!$rowItem) {
10994
            // Try one more time with "id"
10995
            /** @var CLpItem $rowItem */
10996
            $rowItem = $lpItemRepo->findOneBy([
10997
                'cId' => $course_id,
10998
                'lpId' => $learningPathId,
10999
                'id' => $id_in_path,
11000
            ]);
11001
11002
            if (!$rowItem) {
11003
                return -1;
11004
            }
11005
        }
11006
11007
        $type = $rowItem->getItemType();
11008
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
11009
        $main_dir_path = api_get_path(WEB_CODE_PATH);
11010
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
11011
        $link = '';
11012
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
11013
11014
        switch ($type) {
11015
            case 'dir':
11016
                return $main_dir_path.'lp/blank.php';
11017
            case TOOL_CALENDAR_EVENT:
11018
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
11019
            case TOOL_ANNOUNCEMENT:
11020
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
11021
            case TOOL_LINK:
11022
                $linkInfo = Link::getLinkInfo($id);
11023
                if (isset($linkInfo['url'])) {
11024
                    return $linkInfo['url'];
11025
                }
11026
11027
                return '';
11028
            case TOOL_QUIZ:
11029
                if (empty($id)) {
11030
                    return '';
11031
                }
11032
11033
                // Get the lp_item_view with the highest view_count.
11034
                $learnpathItemViewResult = $em
11035
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
11036
                    ->findBy(
11037
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
11038
                        ['viewCount' => 'DESC'],
11039
                        1
11040
                    );
11041
                /** @var CLpItemView $learnpathItemViewData */
11042
                $learnpathItemViewData = current($learnpathItemViewResult);
11043
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
11044
11045
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
11046
                    .http_build_query([
11047
                        'lp_init' => 1,
11048
                        'learnpath_item_view_id' => $learnpathItemViewId,
11049
                        'learnpath_id' => $learningPathId,
11050
                        'learnpath_item_id' => $id_in_path,
11051
                        'exerciseId' => $id,
11052
                    ]);
11053
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
11054
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
11055
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
11056
                $myrow = Database::fetch_array($result);
11057
                $path = $myrow['path'];
11058
11059
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
11060
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
11061
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
11062
            case TOOL_FORUM:
11063
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
11064
            case TOOL_THREAD:
11065
                // forum post
11066
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
11067
                if (empty($id)) {
11068
                    return '';
11069
                }
11070
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
11071
                $result = Database::query($sql);
11072
                $myrow = Database::fetch_array($result);
11073
11074
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
11075
                    .$extraParams;
11076
            case TOOL_POST:
11077
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11078
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
11079
                $myrow = Database::fetch_array($result);
11080
11081
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
11082
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
11083
            case TOOL_READOUT_TEXT:
11084
                return api_get_path(WEB_CODE_PATH).
11085
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
11086
            case TOOL_DOCUMENT:
11087
                $repo = Container::getDocumentRepository();
11088
                $document = $repo->find($rowItem->getPath());
11089
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
11090
11091
                return $file;
11092
11093
                $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...
11094
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
11095
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
11096
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
11097
11098
                $openmethod = 2;
11099
                $officedoc = false;
11100
                Session::write('openmethod', $openmethod);
11101
                Session::write('officedoc', $officedoc);
11102
11103
                if ($showDirectUrl) {
11104
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
11105
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
11106
                        if (Link::isPdfLink($file)) {
11107
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
11108
11109
                            return $pdfUrl;
11110
                        }
11111
                    }
11112
11113
                    return $file;
11114
                }
11115
11116
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
11117
            case TOOL_LP_FINAL_ITEM:
11118
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
11119
                    .$extraParams;
11120
            case 'assignments':
11121
                return $main_dir_path.'work/work.php?'.$extraParams;
11122
            case TOOL_DROPBOX:
11123
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
11124
            case 'introduction_text': //DEPRECATED
11125
                return '';
11126
            case TOOL_COURSE_DESCRIPTION:
11127
                return $main_dir_path.'course_description?'.$extraParams;
11128
            case TOOL_GROUP:
11129
                return $main_dir_path.'group/group.php?'.$extraParams;
11130
            case TOOL_USER:
11131
                return $main_dir_path.'user/user.php?'.$extraParams;
11132
            case TOOL_STUDENTPUBLICATION:
11133
                if (!empty($rowItem->getPath())) {
11134
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11135
                }
11136
11137
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11138
        }
11139
11140
        return $link;
11141
    }
11142
11143
    /**
11144
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11145
     *
11146
     * @author Yannick Warnier <[email protected]>
11147
     *
11148
     * @param string $course_code    Course code
11149
     * @param int    $learningPathId
11150
     * @param int    $id_in_path     The resource ID
11151
     *
11152
     * @return string
11153
     */
11154
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11155
    {
11156
        $_course = api_get_course_info($course_code);
11157
        if (empty($_course)) {
11158
            return '';
11159
        }
11160
        $course_id = $_course['real_id'];
11161
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11162
        $learningPathId = (int) $learningPathId;
11163
        $id_in_path = (int) $id_in_path;
11164
11165
        $sql = "SELECT item_type, title, ref
11166
                FROM $tbl_lp_item
11167
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11168
        $res_item = Database::query($sql);
11169
11170
        if (Database::num_rows($res_item) < 1) {
11171
            return '';
11172
        }
11173
        $row_item = Database::fetch_array($res_item);
11174
        $type = strtolower($row_item['item_type']);
11175
        $id = $row_item['ref'];
11176
        $output = '';
11177
11178
        switch ($type) {
11179
            case TOOL_CALENDAR_EVENT:
11180
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11181
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11182
                $myrow = Database::fetch_array($result);
11183
                $output = $myrow['title'];
11184
                break;
11185
            case TOOL_ANNOUNCEMENT:
11186
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11187
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11188
                $myrow = Database::fetch_array($result);
11189
                $output = $myrow['title'];
11190
                break;
11191
            case TOOL_LINK:
11192
                // Doesn't take $target into account.
11193
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11194
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11195
                $myrow = Database::fetch_array($result);
11196
                $output = $myrow['title'];
11197
                break;
11198
            case TOOL_QUIZ:
11199
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11200
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11201
                $myrow = Database::fetch_array($result);
11202
                $output = $myrow['title'];
11203
                break;
11204
            case TOOL_FORUM:
11205
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11206
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11207
                $myrow = Database::fetch_array($result);
11208
                $output = $myrow['forum_name'];
11209
                break;
11210
            case TOOL_THREAD:
11211
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11212
                // Grabbing the title of the post.
11213
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11214
                $result_title = Database::query($sql_title);
11215
                $myrow_title = Database::fetch_array($result_title);
11216
                $output = $myrow_title['post_title'];
11217
                break;
11218
            case TOOL_POST:
11219
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11220
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11221
                $result = Database::query($sql);
11222
                $post = Database::fetch_array($result);
11223
                $output = $post['post_title'];
11224
                break;
11225
            case 'dir':
11226
            case TOOL_DOCUMENT:
11227
                $title = $row_item['title'];
11228
                $output = '-';
11229
                if (!empty($title)) {
11230
                    $output = $title;
11231
                }
11232
                break;
11233
            case 'hotpotatoes':
11234
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11235
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11236
                $myrow = Database::fetch_array($result);
11237
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11238
                $last = count($pathname) - 1; // Making a correct name for the link.
11239
                $filename = $pathname[$last]; // Making a correct name for the link.
11240
                $myrow['path'] = rawurlencode($myrow['path']);
11241
                $output = $filename;
11242
                break;
11243
        }
11244
11245
        return stripslashes($output);
11246
    }
11247
11248
    /**
11249
     * Get the parent names for the current item.
11250
     *
11251
     * @param int $newItemId Optional. The item ID
11252
     *
11253
     * @return array
11254
     */
11255
    public function getCurrentItemParentNames($newItemId = 0)
11256
    {
11257
        $newItemId = $newItemId ?: $this->get_current_item_id();
11258
        $return = [];
11259
        $item = $this->getItem($newItemId);
11260
        $parent = $this->getItem($item->get_parent());
11261
11262
        while ($parent) {
11263
            $return[] = $parent->get_title();
11264
            $parent = $this->getItem($parent->get_parent());
11265
        }
11266
11267
        return array_reverse($return);
11268
    }
11269
11270
    /**
11271
     * Reads and process "lp_subscription_settings" setting.
11272
     *
11273
     * @return array
11274
     */
11275
    public static function getSubscriptionSettings()
11276
    {
11277
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11278
        if (empty($subscriptionSettings)) {
11279
            // By default allow both settings
11280
            $subscriptionSettings = [
11281
                'allow_add_users_to_lp' => true,
11282
                'allow_add_users_to_lp_category' => true,
11283
            ];
11284
        } else {
11285
            $subscriptionSettings = $subscriptionSettings['options'];
11286
        }
11287
11288
        return $subscriptionSettings;
11289
    }
11290
11291
    /**
11292
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11293
     */
11294
    public function exportToCourseBuildFormat()
11295
    {
11296
        if (!api_is_allowed_to_edit()) {
11297
            return false;
11298
        }
11299
11300
        $courseBuilder = new CourseBuilder();
11301
        $itemList = [];
11302
        /** @var learnpathItem $item */
11303
        foreach ($this->items as $item) {
11304
            $itemList[$item->get_type()][] = $item->get_path();
11305
        }
11306
11307
        if (empty($itemList)) {
11308
            return false;
11309
        }
11310
11311
        if (isset($itemList['document'])) {
11312
            // Get parents
11313
            foreach ($itemList['document'] as $documentId) {
11314
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11315
                if (!empty($documentInfo['parents'])) {
11316
                    foreach ($documentInfo['parents'] as $parentInfo) {
11317
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11318
                            continue;
11319
                        }
11320
                        $itemList['document'][] = $parentInfo['iid'];
11321
                    }
11322
                }
11323
            }
11324
11325
            $courseInfo = api_get_course_info();
11326
            foreach ($itemList['document'] as $documentId) {
11327
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11328
                $items = DocumentManager::get_resources_from_source_html(
11329
                    $documentInfo['absolute_path'],
11330
                    true,
11331
                    TOOL_DOCUMENT
11332
                );
11333
11334
                if (!empty($items)) {
11335
                    foreach ($items as $item) {
11336
                        // Get information about source url
11337
                        $url = $item[0]; // url
11338
                        $scope = $item[1]; // scope (local, remote)
11339
                        $type = $item[2]; // type (rel, abs, url)
11340
11341
                        $origParseUrl = parse_url($url);
11342
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11343
11344
                        if ('local' == $scope) {
11345
                            if ('abs' == $type || 'rel' == $type) {
11346
                                $documentFile = strstr($realOrigPath, 'document');
11347
                                if (false !== strpos($realOrigPath, $documentFile)) {
11348
                                    $documentFile = str_replace('document', '', $documentFile);
11349
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11350
                                    // Document found! Add it to the list
11351
                                    if ($itemDocumentId) {
11352
                                        $itemList['document'][] = $itemDocumentId;
11353
                                    }
11354
                                }
11355
                            }
11356
                        }
11357
                    }
11358
                }
11359
            }
11360
11361
            $courseBuilder->build_documents(
11362
                api_get_session_id(),
11363
                $this->get_course_int_id(),
11364
                true,
11365
                $itemList['document']
11366
            );
11367
        }
11368
11369
        if (isset($itemList['quiz'])) {
11370
            $courseBuilder->build_quizzes(
11371
                api_get_session_id(),
11372
                $this->get_course_int_id(),
11373
                true,
11374
                $itemList['quiz']
11375
            );
11376
        }
11377
11378
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11379
11380
        /*if (!empty($itemList['thread'])) {
11381
            $postList = [];
11382
            foreach ($itemList['thread'] as $postId) {
11383
                $post = get_post_information($postId);
11384
                if ($post) {
11385
                    if (!isset($itemList['forum'])) {
11386
                        $itemList['forum'] = [];
11387
                    }
11388
                    $itemList['forum'][] = $post['forum_id'];
11389
                    $postList[] = $postId;
11390
                }
11391
            }
11392
11393
            if (!empty($postList)) {
11394
                $courseBuilder->build_forum_posts(
11395
                    $this->get_course_int_id(),
11396
                    null,
11397
                    null,
11398
                    $postList
11399
                );
11400
            }
11401
        }*/
11402
11403
        if (!empty($itemList['thread'])) {
11404
            $threadList = [];
11405
            $em = Database::getManager();
11406
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11407
            foreach ($itemList['thread'] as $threadId) {
11408
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11409
                $thread = $repo->find($threadId);
11410
                if ($thread) {
11411
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11412
                    $threadList[] = $thread->getIid();
11413
                }
11414
            }
11415
11416
            if (!empty($threadList)) {
11417
                $courseBuilder->build_forum_topics(
11418
                    api_get_session_id(),
11419
                    $this->get_course_int_id(),
11420
                    null,
11421
                    $threadList
11422
                );
11423
            }
11424
        }
11425
11426
        $forumCategoryList = [];
11427
        if (isset($itemList['forum'])) {
11428
            foreach ($itemList['forum'] as $forumId) {
11429
                $forumInfo = get_forums($forumId);
11430
                $forumCategoryList[] = $forumInfo['forum_category'];
11431
            }
11432
        }
11433
11434
        if (!empty($forumCategoryList)) {
11435
            $courseBuilder->build_forum_category(
11436
                api_get_session_id(),
11437
                $this->get_course_int_id(),
11438
                true,
11439
                $forumCategoryList
11440
            );
11441
        }
11442
11443
        if (!empty($itemList['forum'])) {
11444
            $courseBuilder->build_forums(
11445
                api_get_session_id(),
11446
                $this->get_course_int_id(),
11447
                true,
11448
                $itemList['forum']
11449
            );
11450
        }
11451
11452
        if (isset($itemList['link'])) {
11453
            $courseBuilder->build_links(
11454
                api_get_session_id(),
11455
                $this->get_course_int_id(),
11456
                true,
11457
                $itemList['link']
11458
            );
11459
        }
11460
11461
        if (!empty($itemList['student_publication'])) {
11462
            $courseBuilder->build_works(
11463
                api_get_session_id(),
11464
                $this->get_course_int_id(),
11465
                true,
11466
                $itemList['student_publication']
11467
            );
11468
        }
11469
11470
        $courseBuilder->build_learnpaths(
11471
            api_get_session_id(),
11472
            $this->get_course_int_id(),
11473
            true,
11474
            [$this->get_id()],
11475
            false
11476
        );
11477
11478
        $courseBuilder->restoreDocumentsFromList();
11479
11480
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11481
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11482
        $result = DocumentManager::file_send_for_download(
11483
            $zipPath,
11484
            true,
11485
            $this->get_name().'.zip'
11486
        );
11487
11488
        if ($result) {
11489
            api_not_allowed();
11490
        }
11491
11492
        return true;
11493
    }
11494
11495
    /**
11496
     * Get whether this is a learning path with the accumulated work time or not.
11497
     *
11498
     * @return int
11499
     */
11500
    public function getAccumulateWorkTime()
11501
    {
11502
        return (int) $this->accumulateWorkTime;
11503
    }
11504
11505
    /**
11506
     * Get whether this is a learning path with the accumulated work time or not.
11507
     *
11508
     * @return int
11509
     */
11510
    public function getAccumulateWorkTimeTotalCourse()
11511
    {
11512
        $table = Database::get_course_table(TABLE_LP_MAIN);
11513
        $sql = "SELECT SUM(accumulate_work_time) AS total
11514
                FROM $table
11515
                WHERE c_id = ".$this->course_int_id;
11516
        $result = Database::query($sql);
11517
        $row = Database::fetch_array($result);
11518
11519
        return (int) $row['total'];
11520
    }
11521
11522
    /**
11523
     * Set whether this is a learning path with the accumulated work time or not.
11524
     *
11525
     * @param int $value (0 = false, 1 = true)
11526
     *
11527
     * @return bool
11528
     */
11529
    public function setAccumulateWorkTime($value)
11530
    {
11531
        if (!api_get_configuration_value('lp_minimum_time')) {
11532
            return false;
11533
        }
11534
11535
        $this->accumulateWorkTime = (int) $value;
11536
        $table = Database::get_course_table(TABLE_LP_MAIN);
11537
        $lp_id = $this->get_id();
11538
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11539
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11540
        Database::query($sql);
11541
11542
        return true;
11543
    }
11544
11545
    /**
11546
     * @param int $lpId
11547
     * @param int $courseId
11548
     *
11549
     * @return mixed
11550
     */
11551
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11552
    {
11553
        $lpId = (int) $lpId;
11554
        $courseId = (int) $courseId;
11555
11556
        $table = Database::get_course_table(TABLE_LP_MAIN);
11557
        $sql = "SELECT accumulate_work_time
11558
                FROM $table
11559
                WHERE c_id = $courseId AND id = $lpId";
11560
        $result = Database::query($sql);
11561
        $row = Database::fetch_array($result);
11562
11563
        return $row['accumulate_work_time'];
11564
    }
11565
11566
    /**
11567
     * @param int $courseId
11568
     *
11569
     * @return int
11570
     */
11571
    public static function getAccumulateWorkTimeTotal($courseId)
11572
    {
11573
        $table = Database::get_course_table(TABLE_LP_MAIN);
11574
        $courseId = (int) $courseId;
11575
        $sql = "SELECT SUM(accumulate_work_time) AS total
11576
                FROM $table
11577
                WHERE c_id = $courseId";
11578
        $result = Database::query($sql);
11579
        $row = Database::fetch_array($result);
11580
11581
        return (int) $row['total'];
11582
    }
11583
11584
    /**
11585
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11586
     * and put the images in.
11587
     *
11588
     * @return array
11589
     */
11590
    public static function getIconSelect()
11591
    {
11592
        $theme = api_get_visual_theme();
11593
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11594
        $icons = ['' => get_lang('Please select an option')];
11595
11596
        if (is_dir($path)) {
11597
            $finder = new Finder();
11598
            $finder->files()->in($path);
11599
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11600
            /** @var SplFileInfo $file */
11601
            foreach ($finder as $file) {
11602
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11603
                    $icons[$file->getFilename()] = $file->getFilename();
11604
                }
11605
            }
11606
        }
11607
11608
        return $icons;
11609
    }
11610
11611
    /**
11612
     * @param int $lpId
11613
     *
11614
     * @return string
11615
     */
11616
    public static function getSelectedIcon($lpId)
11617
    {
11618
        $extraFieldValue = new ExtraFieldValue('lp');
11619
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11620
        $icon = '';
11621
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11622
            $icon = $lpIcon['value'];
11623
        }
11624
11625
        return $icon;
11626
    }
11627
11628
    /**
11629
     * @param int $lpId
11630
     *
11631
     * @return string
11632
     */
11633
    public static function getSelectedIconHtml($lpId)
11634
    {
11635
        $icon = self::getSelectedIcon($lpId);
11636
11637
        if (empty($icon)) {
11638
            return '';
11639
        }
11640
11641
        $theme = api_get_visual_theme();
11642
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11643
11644
        return Display::img($path);
11645
    }
11646
11647
    /**
11648
     * @param string $value
11649
     *
11650
     * @return string
11651
     */
11652
    public function cleanItemTitle($value)
11653
    {
11654
        $value = Security::remove_XSS(strip_tags($value));
11655
11656
        return $value;
11657
    }
11658
11659
    public function setItemTitle(FormValidator $form)
11660
    {
11661
        if (api_get_configuration_value('save_titles_as_html')) {
11662
            $form->addHtmlEditor(
11663
                'title',
11664
                get_lang('Title'),
11665
                true,
11666
                false,
11667
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11668
            );
11669
        } else {
11670
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11671
            $form->applyFilter('title', 'trim');
11672
            $form->applyFilter('title', 'html_filter');
11673
        }
11674
    }
11675
11676
    /**
11677
     * @return array
11678
     */
11679
    public function getItemsForForm($addParentCondition = false)
11680
    {
11681
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11682
        $course_id = api_get_course_int_id();
11683
11684
        $sql = "SELECT * FROM $tbl_lp_item
11685
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11686
11687
        if ($addParentCondition) {
11688
            $sql .= ' AND parent_item_id = 0 ';
11689
        }
11690
        $sql .= ' ORDER BY display_order ASC';
11691
11692
        $result = Database::query($sql);
11693
        $arrLP = [];
11694
        while ($row = Database::fetch_array($result)) {
11695
            $arrLP[] = [
11696
                'iid' => $row['iid'],
11697
                'id' => $row['iid'],
11698
                'item_type' => $row['item_type'],
11699
                'title' => $this->cleanItemTitle($row['title']),
11700
                'title_raw' => $row['title'],
11701
                'path' => $row['path'],
11702
                'description' => Security::remove_XSS($row['description']),
11703
                'parent_item_id' => $row['parent_item_id'],
11704
                'previous_item_id' => $row['previous_item_id'],
11705
                'next_item_id' => $row['next_item_id'],
11706
                'display_order' => $row['display_order'],
11707
                'max_score' => $row['max_score'],
11708
                'min_score' => $row['min_score'],
11709
                'mastery_score' => $row['mastery_score'],
11710
                'prerequisite' => $row['prerequisite'],
11711
                'max_time_allowed' => $row['max_time_allowed'],
11712
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11713
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11714
            ];
11715
        }
11716
11717
        return $arrLP;
11718
    }
11719
11720
    /**
11721
     * Gets whether this SCORM learning path has been marked to use the score
11722
     * as progress. Takes into account whether the learnpath matches (SCORM
11723
     * content + less than 2 items).
11724
     *
11725
     * @return bool True if the score should be used as progress, false otherwise
11726
     */
11727
    public function getUseScoreAsProgress()
11728
    {
11729
        // If not a SCORM, we don't care about the setting
11730
        if ($this->get_type() != 2) {
11731
            return false;
11732
        }
11733
        // If more than one step in the SCORM, we don't care about the setting
11734
        if ($this->get_total_items_count() > 1) {
11735
            return false;
11736
        }
11737
        $extraFieldValue = new ExtraFieldValue('lp');
11738
        $doUseScore = false;
11739
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11740
        if (!empty($useScore) && isset($useScore['value'])) {
11741
            $doUseScore = $useScore['value'];
11742
        }
11743
11744
        return $doUseScore;
11745
    }
11746
11747
    /**
11748
     * Get the user identifier (user_id or username
11749
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11750
     *
11751
     * @return string User ID or username, depending on configuration setting
11752
     */
11753
    public static function getUserIdentifierForExternalServices()
11754
    {
11755
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11756
            return api_get_user_info(api_get_user_id())['username'];
11757
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
11758
            $extraFieldValue = new ExtraFieldValue('user');
11759
            $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'));
11760
11761
            return $extrafield['value'];
11762
        } else {
11763
            return api_get_user_id();
11764
        }
11765
    }
11766
11767
    /**
11768
     * Save the new order for learning path items.
11769
     *
11770
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11771
     *
11772
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11773
     * @param int   $courseId
11774
     */
11775
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11776
    {
11777
        $courseId = $courseId ?: api_get_course_int_id();
11778
        $itemList = new LpItemOrderList();
11779
11780
        foreach ($orderList as $id => $parentId) {
11781
            $item = new LpOrderItem($id, $parentId);
11782
            $itemList->add($item);
11783
        }
11784
11785
        $parents = $itemList->getListOfParents();
11786
11787
        foreach ($parents as $parentId) {
11788
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11789
            $previous_item_id = 0;
11790
            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...
11791
                $item_id = $sameParentLpItemList->list[$i]->id;
11792
                // display_order
11793
                $display_order = $i + 1;
11794
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11795
                // previous_item_id
11796
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11797
                $previous_item_id = $item_id;
11798
                // next_item_id
11799
                $next_item_id = 0;
11800
                if ($i < count($sameParentLpItemList->list) - 1) {
11801
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11802
                }
11803
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11804
            }
11805
        }
11806
11807
        $table = Database::get_course_table(TABLE_LP_ITEM);
11808
11809
        foreach ($itemList->list as $item) {
11810
            $params = [];
11811
            $params['display_order'] = $item->display_order;
11812
            $params['previous_item_id'] = $item->previous_item_id;
11813
            $params['next_item_id'] = $item->next_item_id;
11814
            $params['parent_item_id'] = $item->parent_item_id;
11815
11816
            Database::update(
11817
                $table,
11818
                $params,
11819
                [
11820
                    'iid = ? AND c_id = ? ' => [
11821
                        (int) $item->id,
11822
                        (int) $courseId,
11823
                    ],
11824
                ]
11825
            );
11826
        }
11827
    }
11828
11829
    /**
11830
     * Get the depth level of LP item.
11831
     *
11832
     * @param array $items
11833
     * @param int   $currentItemId
11834
     *
11835
     * @return int
11836
     */
11837
    private static function get_level_for_item($items, $currentItemId)
11838
    {
11839
        $parentItemId = 0;
11840
        if (isset($items[$currentItemId])) {
11841
            $parentItemId = $items[$currentItemId]->parent;
11842
        }
11843
11844
        if (0 == $parentItemId) {
11845
            return 0;
11846
        } else {
11847
            return self::get_level_for_item($items, $parentItemId) + 1;
11848
        }
11849
    }
11850
11851
    /**
11852
     * Generate the link for a learnpath category as course tool.
11853
     *
11854
     * @param int $categoryId
11855
     *
11856
     * @return string
11857
     */
11858
    private static function getCategoryLinkForTool($categoryId)
11859
    {
11860
        $categoryId = (int) $categoryId;
11861
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11862
            .http_build_query(
11863
                [
11864
                    'action' => 'view_category',
11865
                    'id' => $categoryId,
11866
                ]
11867
            );
11868
11869
        return $link;
11870
    }
11871
11872
    /**
11873
     * Return the scorm item type object with spaces replaced with _
11874
     * The return result is use to build a css classname like scorm_type_$return.
11875
     *
11876
     * @param $in_type
11877
     *
11878
     * @return mixed
11879
     */
11880
    private static function format_scorm_type_item($in_type)
11881
    {
11882
        return str_replace(' ', '_', $in_type);
11883
    }
11884
11885
    /**
11886
     * Check and obtain the lp final item if exist.
11887
     *
11888
     * @return learnpathItem
11889
     */
11890
    private function getFinalItem()
11891
    {
11892
        if (empty($this->items)) {
11893
            return null;
11894
        }
11895
11896
        foreach ($this->items as $item) {
11897
            if ('final_item' !== $item->type) {
11898
                continue;
11899
            }
11900
11901
            return $item;
11902
        }
11903
    }
11904
11905
    /**
11906
     * Get the LP Final Item Template.
11907
     *
11908
     * @return string
11909
     */
11910
    private function getFinalItemTemplate()
11911
    {
11912
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11913
    }
11914
11915
    /**
11916
     * Get the LP Final Item Url.
11917
     *
11918
     * @return string
11919
     */
11920
    private function getSavedFinalItem()
11921
    {
11922
        $finalItem = $this->getFinalItem();
11923
11924
        $repo = Container::getDocumentRepository();
11925
        /** @var CDocument $document */
11926
        $document = $repo->find($finalItem->path);
11927
11928
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11929
            return  $repo->getResourceFileContent($document);
11930
        }
11931
11932
        return '';
11933
    }
11934
}
11935