Completed
Push — master ( 9b8b24...6e1754 )
by Julito
58:58
created

learnpath   F

Complexity

Total Complexity 1766

Size/Duplication

Total Lines 12527
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12527
rs 0.6314
c 0
b 0
f 0
wmc 1766

205 Methods

Rating   Name   Duplication   Size   Complexity  
A getProgressBar() 0 4 1
C update_default_view_mode() 0 39 8
B next() 0 22 5
B get_iv_objectives_array() 0 32 3
A getCategory() 0 7 1
D display_edit_item() 0 116 17
B set_autolaunch() 0 27 2
F display_thread_form() 0 216 42
A get_total_items_count() 0 6 2
B getCalculateScore() 0 47 6
D save_last() 0 44 9
A get_first_item_id() 0 7 2
A get_update_queue() 0 6 2
B copy() 0 26 1
A getCategoryFromCourseIntoSelect() 0 15 4
F get_link() 0 313 57
B switch_attempt_mode() 0 21 5
A getAccumulateScormTime() 0 3 1
F create_document() 0 167 30
A get_course_int_id() 0 3 2
C prerequisites_match() 0 54 11
A previous() 0 14 2
A tree_array() 0 7 2
A createCategory() 0 8 1
B move_down() 0 54 8
B get_exercises() 0 103 5
B fixBlockedLinks() 0 55 9
A open() 0 12 2
F edit_item() 0 237 24
A set_jslib() 0 16 3
A createForum() 0 21 1
B update_default_scorm_commit() 0 31 6
C getChildrenToc() 0 61 12
A set_error_msg() 0 9 3
C stop_previous_item() 0 54 17
C set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
C start_current_item() 0 27 13
B set_publicated_on() 0 30 5
D set_current_item() 0 31 9
A get_author() 0 9 3
B display_lp_prerequisites_list() 0 31 5
A set_prerequisite() 0 15 3
A set_modified_on() 0 16 3
A get_last() 0 12 3
A getHideTableOfContents() 0 3 1
B categoryIsPublished() 0 26 2
A get_proximity() 0 9 3
B get_type() 0 13 6
A get_maker() 0 9 3
F display_link_form() 0 206 39
F display_document_form() 0 411 84
B getForum() 0 44 3
A get_progress_bar() 0 12 1
A set_preview_image() 0 17 3
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
B getSiblingDirectories() 0 35 6
B get_brother_items() 0 29 6
C toggle_publish() 0 80 11
A getCountCategories() 0 10 2
A set_course_int_id() 0 3 1
A moveDownCategory() 0 11 2
A get_type_static() 0 15 3
A sort_tree_array() 0 12 3
F scorm_export() 0 922 107
A get_items_status_list() 0 12 3
A get_theme() 0 9 3
F display_item_form() 0 222 36
B select_previous_item_id() 0 25 2
F add_item() 0 237 13
C get_previous_index() 0 23 7
B set_seriousgame_mode() 0 28 6
A get_preview_image() 0 9 3
A isBlockedByPrerequisite() 0 21 3
F get_progress_bar_text() 0 43 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 7 2
F first() 0 71 22
A getItem() 0 7 3
C get_package_type() 0 74 18
A getCategoryId() 0 3 1
F create_tree_array() 0 42 12
B get_links() 0 109 6
A get_items_details_as_js() 0 13 4
A get_progress_bar_mode() 0 9 3
C set_terms_by_prefix() 0 66 10
A get_user_id() 0 9 3
A set_use_max_score() 0 19 3
A toggle_visibility() 0 14 2
B create_path() 0 14 5
A get_current_item_id() 0 13 4
A set_previous_item() 0 6 2
B getCurrentBuildingModeURL() 0 10 5
A display_document() 0 19 2
A setSubscribeUsers() 0 13 2
B upload_image() 0 39 6
B save_current() 0 31 6
A set_theme() 0 16 3
A getChapterTypes() 0 4 1
F __construct() 0 332 54
D rl_get_resource_name() 0 98 14
A getSavedFinalItem() 0 12 3
C get_preview_image_path() 0 28 7
B restart() 0 39 6
A set_author() 0 16 3
B get_iv_interactions_array() 0 42 4
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 14 3
A getSubscribeUsers() 0 3 1
B set_attempt_mode() 0 32 5
A close() 0 15 3
B deleteCategory() 0 32 4
B update_display_order() 0 27 5
B generate_lp_folder() 0 59 8
F display_quiz_form() 0 207 42
A moveUpCategory() 0 11 2
C get_mediaplayer() 0 70 10
A set_proximity() 0 19 4
D display_item() 0 98 15
C overview() 0 54 10
B set_expired_on() 0 31 5
F autocomplete_parents() 0 103 18
C move_up() 0 51 8
A get_interactions_count_from_db() 0 15 2
C get_scorm_prereq_string() 0 70 10
D display_item_prerequisites_form() 0 140 16
D print_recursive() 0 38 10
A clear_prerequisites() 0 17 2
C scorm_export_to_pdf() 0 62 12
C isFirstOrLastItem() 0 36 7
C display_move_item() 0 57 11
C getCalculateStars() 0 80 12
F display_hotpotatoes_form() 0 183 39
B get_scorm_xml_node() 0 20 7
A has_audio() 0 13 4
B set_encoding() 0 21 5
D edit_item_prereq() 0 46 10
B get_js_info() 0 43 6
A get_id() 0 6 2
B get_js_dropdown_array() 0 51 6
A getFinalEvaluationItem() 0 12 3
B build_action_menu() 0 118 5
B get_next_item_id() 0 18 6
B get_view() 0 41 6
A getCategoryByCourse() 0 8 1
F is_lp_visible_for_student() 0 123 21
B get_flat_ordered_items_list() 0 34 5
A getProgress() 0 22 2
A display_resources() 0 49 1
A getCategoryLinkForTool() 0 11 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 21 8
F display_manipulate() 0 116 15
B get_complete_items_count() 0 26 6
A display_item_small_form() 0 13 1
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 89 1
C delete_item() 0 73 9
C get_forums() 0 112 11
D edit_document() 0 60 14
F move_item() 0 152 27
A getTotalItemsCountWithoutDirs() 0 13 4
B delete_lp_image() 0 15 5
F display_forum_form() 0 207 40
B update_scorm_debug() 0 28 6
B toggleCategoryVisibility() 0 29 3
C getParentToc() 0 67 14
F add_lp() 0 146 14
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 14 2
B update_reinit() 0 29 6
A get_progress() 0 9 3
A getFinalItemTemplate() 0 3 1
F getListArrayToc() 0 79 12
A get_extension() 0 4 1
D rl_get_resource_link_for_learnpath() 0 149 29
A getCourseCode() 0 3 1
A get_view_id() 0 9 3
A getLpFromSession() 0 13 3
C toggleCategoryPublish() 0 84 8
B get_navigation_bar() 0 50 4
B generate_learning_path_folder() 0 28 4
A set_hide_toc_frame() 0 20 4
A getCategories() 0 17 1
D get_next_index() 0 31 10
B get_attempt_mode() 0 20 9
A get_common_index_terms_by_prefix() 0 16 3
A get_lp_session_id() 0 9 3
F return_new_tree() 0 399 48
A setCategoryId() 0 11 1
A get_previous_item_id() 0 7 2
B get_toc() 0 26 5
A autosave() 0 4 2
C categoryIsVisibleForStudent() 0 59 11
A get_level_for_item() 0 7 2
D delete() 0 110 18
B get_student_publications() 0 44 4
A set_maker() 0 18 4
A get_name() 0 9 3
B lpHasForum() 0 26 1
F display_student_publication_form() 0 198 38
B delete_children_items() 0 22 5
D save_item() 0 44 9
B set_name() 0 33 5

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
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
use Chamilo\CourseBundle\Entity\CLpCategory;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
8
use Gedmo\Sortable\Entity\Repository\SortableRepository;
9
use Symfony\Component\Filesystem\Filesystem;
10
use Symfony\Component\Finder\Finder;
11
use Chamilo\CourseBundle\Entity\CLp;
12
use Chamilo\CourseBundle\Entity\CTool;
13
use Chamilo\UserBundle\Entity\User;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, User. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CItemProperty;
17
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
18
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
19
20
/**
21
 * Class learnpath
22
 * This class defines the parent attributes and methods for Chamilo learnpaths
23
 * and SCORM learnpaths. It is used by the scorm class.
24
 * @todo decouple class
25
 * @package chamilo.learnpath
26
 * @author  Yannick Warnier <[email protected]>
27
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
28
 */
29
class learnpath
30
{
31
    public $attempt = 0; // The number for the current ID view.
32
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
33
    public $current; // Id of the current item the user is viewing.
34
    public $current_score; // The score of the current item.
35
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
36
    public $current_time_stop; // The time the user closed this resource.
37
    public $default_status = 'not attempted';
38
    public $encoding = 'UTF-8';
39
    public $error = '';
40
    public $extra_information = ''; // This string can be used by proprietary SCORM contents to store data about the current learnpath.
41
    public $force_commit = false; // For SCORM only - if set to true, will send a scorm LMSCommit() request on each LMSSetValue().
42
    public $index; // The index of the active learnpath_item in $ordered_items array.
43
    public $items = [];
44
    public $last; // item_id of last item viewed in the learning path.
45
    public $last_item_seen = 0; // In case we have already come in this learnpath, reuse the last item seen if authorized.
46
    public $license; // Which license this course has been given - not used yet on 20060522.
47
    public $lp_id; // DB iid for this learnpath.
48
    public $lp_view_id; // DB ID for lp_view
49
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
50
    public $message = '';
51
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
52
    public $name; // Learnpath name (they generally have one).
53
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
54
    public $path = ''; // Path inside the scorm directory (if scorm).
55
    public $theme; // The current theme of the learning path.
56
    public $preview_image; // The current image of the learning path.
57
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
58
59
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
60
    public $prevent_reinit = 1;
61
62
    // Describes the mode of progress bar display.
63
    public $seriousgame_mode = 0;
64
    public $progress_bar_mode = '%';
65
66
    // Percentage progress as saved in the db.
67
    public $progress_db = 0;
68
    public $proximity; // Wether the content is distant or local or unknown.
69
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
70
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
71
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
72
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
73
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
74
    public $user_id; //ID of the user that is viewing/using the course
75
    public $update_queue = [];
76
    public $scorm_debug = 0;
77
    public $arrMenu = []; // Array for the menu items.
78
    public $debug = 0; // Logging level.
79
    public $lp_session_id = 0;
80
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
81
    public $prerequisite = 0;
82
    public $use_max_score = 1; // 1 or 0
83
    public $subscribeUsers = 0; // Subscribe users or not
84
    public $created_on = '';
85
    public $modified_on = '';
86
    public $publicated_on = '';
87
    public $expired_on = '';
88
    public $ref = null;
89
    public $course_int_id;
90
    public $course_info = [];
91
    public $categoryId;
92
93
    /**
94
     * Constructor.
95
     * Needs a database handler, a course code and a learnpath id from the database.
96
     * Also builds the list of items into $this->items.
97
     * @param   string $course Course code
98
     * @param   integer $lp_id
99
     * @param   integer $user_id
100
     */
101
    public function __construct($course, $lp_id, $user_id)
102
    {
103
        $this->encoding = api_get_system_encoding();
104
        if ($this->debug > 0) {
105
            error_log('New LP - In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')', 0);
106
        }
107
        if (empty($course)) {
108
            $course = api_get_course_id();
109
        }
110
        $course_info = api_get_course_info($course);
111
        if (!empty($course_info)) {
112
            $this->cc = $course_info['code'];
113
            $this->course_info = $course_info;
114
            $course_id = $course_info['real_id'];
115
        } else {
116
            $this->error = 'Course code does not exist in database.';
117
        }
118
119
        $lp_id = (int) $lp_id;
120
        $course_id = (int) $course_id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $course_id does not seem to be defined for all execution paths leading up to this point.
Loading history...
121
        $this->set_course_int_id($course_id);
122
        // Check learnpath ID.
123
        if (empty($lp_id) || empty($course_id)) {
124
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
125
        } else {
126
            // TODO: Make it flexible to use any course_code (still using env course code here).
127
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
128
            $sql = "SELECT * FROM $lp_table
129
                    WHERE iid = $lp_id";
130
            if ($this->debug > 2) {
131
                error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
132
            }
133
            $res = Database::query($sql);
134
            if (Database::num_rows($res) > 0) {
135
                $this->lp_id = $lp_id;
136
                $row = Database::fetch_array($res);
137
                $this->type = $row['lp_type'];
138
                $this->name = stripslashes($row['name']);
139
                $this->proximity = $row['content_local'];
140
                $this->theme = $row['theme'];
141
                $this->maker = $row['content_maker'];
142
                $this->prevent_reinit = $row['prevent_reinit'];
143
                $this->seriousgame_mode = $row['seriousgame_mode'];
144
                $this->license = $row['content_license'];
145
                $this->scorm_debug = $row['debug'];
146
                $this->js_lib = $row['js_lib'];
0 ignored issues
show
Bug Best Practice introduced by
The property js_lib does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
147
                $this->path = $row['path'];
148
                $this->preview_image = $row['preview_image'];
149
                $this->author = $row['author'];
0 ignored issues
show
Bug Best Practice introduced by
The property author does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
150
                $this->hide_toc_frame = $row['hide_toc_frame'];
0 ignored issues
show
Bug Best Practice introduced by
The property hide_toc_frame does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
151
                $this->lp_session_id = $row['session_id'];
152
                $this->use_max_score = $row['use_max_score'];
153
                $this->subscribeUsers = $row['subscribe_users'];
154
                $this->created_on = $row['created_on'];
155
                $this->modified_on = $row['modified_on'];
156
                $this->ref = $row['ref'];
157
                $this->categoryId = $row['category_id'];
158
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
159
160
                if (!empty($row['publicated_on'])) {
161
                    $this->publicated_on = $row['publicated_on'];
162
                }
163
164
                if (!empty($row['expired_on'])) {
165
                    $this->expired_on = $row['expired_on'];
166
                }
167
                if ($this->type == 2) {
168
                    if ($row['force_commit'] == 1) {
169
                        $this->force_commit = true;
170
                    }
171
                }
172
                $this->mode = $row['default_view_mod'];
173
174
                // Check user ID.
175
                if (empty($user_id)) {
176
                    $this->error = 'User ID is empty';
177
                } else {
178
                    $user_info = api_get_user_info($user_id);
179
                    if (!empty($user_info)) {
180
                        $this->user_id = $user_info['user_id'];
181
                    } else {
182
                        $this->error = 'User ID does not exist in database ('.$sql.')';
183
                    }
184
                }
185
186
                // End of variables checking.
187
                $session_id = api_get_session_id();
188
                //  Get the session condition for learning paths of the base + session.
189
                $session = api_get_session_condition($session_id);
190
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
191
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
192
193
                // Selecting by view_count descending allows to get the highest view_count first.
194
                $sql = "SELECT * FROM $lp_table
195
                        WHERE 
196
                            c_id = $course_id AND 
197
                            lp_id = $lp_id AND 
198
                            user_id = $user_id 
199
                            $session
200
                        ORDER BY view_count DESC";
201
                $res = Database::query($sql);
202
                if ($this->debug > 2) {
203
                    error_log('New LP - learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
204
                }
205
206
                if (Database::num_rows($res) > 0) {
207
                    if ($this->debug > 2) {
208
                        error_log('New LP - learnpath::__construct() '.__LINE__.' - Found previous view');
209
                    }
210
                    $row = Database::fetch_array($res);
211
                    $this->attempt = $row['view_count'];
212
                    $this->lp_view_id = $row['id'];
213
                    $this->last_item_seen = $row['last_item'];
214
                    $this->progress_db = $row['progress'];
215
                    $this->lp_view_session_id = $row['session_id'];
216
                } elseif (!api_is_invitee()) {
217
                    if ($this->debug > 2) {
218
                        error_log('New LP - learnpath::__construct() '.__LINE__.' - NOT Found previous view');
219
                    }
220
                    $this->attempt = 1;
221
                    $params = [
222
                        'c_id' => $course_id,
223
                        'lp_id' => $lp_id,
224
                        'user_id' => $user_id,
225
                        'view_count' => 1,
226
                        'session_id' => $session_id,
227
                        'last_item' => 0
228
                    ];
229
                    $this->lp_view_id = Database::insert($lp_table, $params);
230
                    if (!empty($this->lp_view_id)) {
231
                        $sql = "UPDATE $lp_table SET id = iid
232
                                WHERE iid = ".$this->lp_view_id;
233
                        Database::query($sql);
234
                    }
235
                }
236
237
                // Initialise items.
238
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
239
                $sql = "SELECT * FROM $lp_item_table
240
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
241
                        ORDER BY parent_item_id, display_order";
242
                $res = Database::query($sql);
243
244
                if ($this->debug > 2) {
245
                    error_log('New LP - learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
246
                    error_log('-- Start while--');
247
                }
248
249
                $lp_item_id_list = [];
250
                while ($row = Database::fetch_array($res)) {
251
                    $lp_item_id_list[] = $row['iid'];
252
                    switch ($this->type) {
253
                        case 3: //aicc
254
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
255
                            if (is_object($oItem)) {
256
                                $my_item_id = $oItem->get_id();
257
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
0 ignored issues
show
Bug introduced by
It seems like $this->lp_view_id can also be of type false; however, parameter $lp_view_id of learnpathItem::set_lp_view() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

257
                                $oItem->set_lp_view(/** @scrutinizer ignore-type */ $this->lp_view_id, $course_id);
Loading history...
258
                                $oItem->set_prevent_reinit($this->prevent_reinit);
259
                                // Don't use reference here as the next loop will make the pointed object change.
260
                                $this->items[$my_item_id] = $oItem;
261
                                $this->refs_list[$oItem->ref] = $my_item_id;
262
                                if ($this->debug > 2) {
263
                                    error_log(
264
                                        'New LP - learnpath::__construct() - '.
265
                                        'aicc object with id '.$my_item_id.
266
                                        ' set in items[]',
267
                                        0
268
                                    );
269
                                }
270
                            }
271
                            break;
272
                        case 2:
273
                            $oItem = new scormItem('db', $row['iid'], $course_id);
274
                            if (is_object($oItem)) {
275
                                $my_item_id = $oItem->get_id();
276
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
277
                                $oItem->set_prevent_reinit($this->prevent_reinit);
278
                                // Don't use reference here as the next loop will make the pointed object change.
279
                                $this->items[$my_item_id] = $oItem;
280
                                $this->refs_list[$oItem->ref] = $my_item_id;
281
                                if ($this->debug > 2) {
282
                                    error_log('New LP - object with id '.$my_item_id.' set in items[]', 0);
283
                                }
284
                            }
285
                            break;
286
                        case 1:
287
                        default:
288
                            if ($this->debug > 2) {
289
                                error_log('New LP - learnpath::__construct() '.__LINE__.' - calling learnpathItem');
290
                            }
291
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
292
293
                            if ($this->debug > 2) {
294
                                error_log('New LP - learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
295
                            }
296
                            if (is_object($oItem)) {
297
                                $my_item_id = $oItem->get_id();
298
                                // Moved down to when we are sure the item_view exists.
299
                                //$oItem->set_lp_view($this->lp_view_id);
300
                                $oItem->set_prevent_reinit($this->prevent_reinit);
301
                                // Don't use reference here as the next loop will make the pointed object change.
302
                                $this->items[$my_item_id] = $oItem;
303
                                $this->refs_list[$my_item_id] = $my_item_id;
304
                                if ($this->debug > 2) {
305
                                    error_log(
306
                                        'New LP - learnpath::__construct() '.__LINE__.
307
                                        ' - object with id '.$my_item_id.' set in items[]'
308
                                    );
309
                                }
310
                            }
311
                            break;
312
                    }
313
314
                    // Setting the object level with variable $this->items[$i][parent]
315
                    foreach ($this->items as $itemLPObject) {
316
                        $level = self::get_level_for_item(
317
                            $this->items,
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type mixed; however, parameter $items of learnpath::get_level_for_item() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

317
                            /** @scrutinizer ignore-type */ $this->items,
Loading history...
318
                            $itemLPObject->db_id
319
                        );
320
                        $itemLPObject->level = $level;
321
                    }
322
323
                    // Setting the view in the item object.
324
                    if (is_object($this->items[$row['iid']])) {
325
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
326
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
327
                            $this->items[$row['iid']]->current_start_time = 0;
328
                            $this->items[$row['iid']]->current_stop_time = 0;
329
                        }
330
                    }
331
                }
332
333
                if ($this->debug > 2) {
334
                    error_log('New LP - learnpath::__construct() '.__LINE__.' ----- end while ----');
335
                }
336
337
                if (!empty($lp_item_id_list)) {
338
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
339
                    if (!empty($lp_item_id_list_to_string)) {
340
                        // Get last viewing vars.
341
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
342
                        // This query should only return one or zero result.
343
                        $sql = "SELECT lp_item_id, status
344
                                FROM $itemViewTable
345
                                WHERE
346
                                    c_id = $course_id AND
347
                                    lp_view_id = ".$this->lp_view_id." AND
0 ignored issues
show
Bug introduced by
Are you sure $this->lp_view_id of type mixed|integer|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

347
                                    lp_view_id = "./** @scrutinizer ignore-type */ $this->lp_view_id." AND
Loading history...
348
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
349
                                ORDER BY view_count DESC ";
350
351
                        if ($this->debug > 2) {
352
                            error_log(
353
                                'New LP - learnpath::__construct() - Selecting item_views: '.$sql,
354
                                0
355
                            );
356
                        }
357
358
                        $status_list = [];
359
                        $res = Database::query($sql);
360
                        while ($row = Database:: fetch_array($res)) {
361
                            $status_list[$row['lp_item_id']] = $row['status'];
362
                        }
363
364
                        foreach ($lp_item_id_list as $item_id) {
365
                            if (isset($status_list[$item_id])) {
366
                                $status = $status_list[$item_id];
367
                                if (is_object($this->items[$item_id])) {
368
                                    $this->items[$item_id]->set_status($status);
369
                                    if (empty($status)) {
370
                                        $this->items[$item_id]->set_status(
371
                                            $this->default_status
372
                                        );
373
                                    }
374
                                }
375
                            } else {
376
                                if (!api_is_invitee()) {
377
                                    if (is_object($this->items[$item_id])) {
378
                                        $this->items[$item_id]->set_status(
379
                                            $this->default_status
380
                                        );
381
                                    }
382
383
                                    if (!empty($this->lp_view_id)) {
384
                                        // Add that row to the lp_item_view table so that
385
                                        // we have something to show in the stats page.
386
                                        $params = [
387
                                            'c_id' => $course_id,
388
                                            'lp_item_id' => $item_id,
389
                                            'lp_view_id' => $this->lp_view_id,
390
                                            'view_count' => 1,
391
                                            'status' => 'not attempted',
392
                                            'start_time' => time(),
393
                                            'total_time' => 0,
394
                                            'score' => 0
395
                                        ];
396
                                        $insertId = Database::insert($itemViewTable, $params);
397
398
                                        if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
399
                                            $sql = "UPDATE $itemViewTable SET id = iid
400
                                                    WHERE iid = $insertId";
401
                                            Database::query($sql);
402
                                        }
403
404
                                        $this->items[$item_id]->set_lp_view(
405
                                            $this->lp_view_id,
406
                                            $course_id
407
                                        );
408
                                    }
409
                                }
410
                            }
411
                        }
412
                    }
413
                }
414
415
                $this->ordered_items = self::get_flat_ordered_items_list(
416
                    $this->get_id(),
417
                    0,
418
                    $course_id
419
                );
420
                $this->max_ordered_items = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property max_ordered_items does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
421
                foreach ($this->ordered_items as $index => $dummy) {
422
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
423
                        $this->max_ordered_items = $index;
424
                    }
425
                }
426
                // TODO: Define the current item better.
427
                $this->first();
428
                if ($this->debug > 2) {
429
                    error_log('New LP - learnpath::__construct() '.__LINE__.' - End of learnpath constructor for learnpath '.$this->get_id(), 0);
430
                }
431
            } else {
432
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
433
            }
434
        }
435
    }
436
437
    /**
438
     * @return string
439
     */
440
    public function getCourseCode()
441
    {
442
        return $this->cc;
443
    }
444
445
    /**
446
     * @return int
447
     */
448
    public function get_course_int_id()
449
    {
450
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
451
    }
452
453
    /**
454
     * @param $course_id
455
     * @return int
456
     */
457
    public function set_course_int_id($course_id)
458
    {
459
        return $this->course_int_id = (int) $course_id;
460
    }
461
462
    /**
463
     * Get the depth level of LP item
464
     * @param array $items
465
     * @param int $currentItemId
466
     * @return int
467
     */
468
    private static function get_level_for_item($items, $currentItemId)
469
    {
470
        $parentItemId = $items[$currentItemId]->parent;
471
        if ($parentItemId == 0) {
472
            return 0;
473
        } else {
474
            return self::get_level_for_item($items, $parentItemId) + 1;
475
        }
476
    }
477
478
    /**
479
     * Function rewritten based on old_add_item() from Yannick Warnier.
480
     * Due the fact that users can decide where the item should come, I had to overlook this function and
481
     * I found it better to rewrite it. Old function is still available.
482
     * Added also the possibility to add a description.
483
     *
484
     * @param int $parent
485
     * @param int $previous
486
     * @param string $type
487
     * @param int $id resource ID (ref)
488
     * @param string $title
489
     * @param string $description
490
     * @param int $prerequisites
491
     * @param int $max_time_allowed
492
     * @param int $userId
493
     *
494
     * @return int
495
     */
496
    public function add_item(
497
        $parent,
498
        $previous,
499
        $type = 'dir',
500
        $id,
501
        $title,
502
        $description,
503
        $prerequisites = 0,
504
        $max_time_allowed = 0,
505
        $userId = 0
506
    ) {
507
        $course_id = $this->course_info['real_id'];
508
        if ($this->debug > 0) {
509
            error_log('New LP - In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
510
        }
511
        if (empty($course_id)) {
512
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
513
            $this->course_info = api_get_course_info($this->cc);
514
            $course_id = $this->course_info['real_id'];
515
        }
516
        $userId = empty($userId) ? api_get_user_id() : $userId;
517
        $sessionId = api_get_session_id();
518
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
519
        $_course = $this->course_info;
520
        $parent = intval($parent);
521
        $previous = intval($previous);
522
        $id = intval($id);
523
        $max_time_allowed = htmlentities($max_time_allowed);
524
        if (empty($max_time_allowed)) {
525
            $max_time_allowed = 0;
526
        }
527
        $sql = "SELECT COUNT(iid) AS num
528
                FROM $tbl_lp_item
529
                WHERE
530
                    c_id = $course_id AND
531
                    lp_id = ".$this->get_id()." AND
532
                    parent_item_id = ".$parent;
533
534
        $res_count = Database::query($sql);
535
        $row = Database::fetch_array($res_count);
536
        $num = $row['num'];
537
538
        if ($num > 0) {
539
            if (empty($previous)) {
540
                $sql = "SELECT iid, next_item_id, display_order
541
                        FROM $tbl_lp_item
542
                        WHERE
543
                            c_id = $course_id AND
544
                            lp_id = ".$this->get_id()." AND
545
                            parent_item_id = $parent AND
546
                            previous_item_id = 0 OR
547
                            previous_item_id = $parent";
548
                $result = Database::query($sql);
549
                $row = Database::fetch_array($result);
550
                $tmp_previous = 0;
551
                $next = $row['iid'];
552
                $display_order = 0;
553
            } else {
554
                $previous = (int) $previous;
555
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
556
						FROM $tbl_lp_item
557
                        WHERE
558
                            c_id = $course_id AND
559
                            lp_id = ".$this->get_id()." AND
560
                            id = $previous";
561
                $result = Database::query($sql);
562
                $row = Database:: fetch_array($result);
563
                $tmp_previous = $row['iid'];
564
                $next = $row['next_item_id'];
565
                $display_order = $row['display_order'];
566
            }
567
        } else {
568
            $tmp_previous = 0;
569
            $next = 0;
570
            $display_order = 0;
571
        }
572
573
        $id = intval($id);
574
        $typeCleaned = Database::escape_string($type);
575
        if ($type == 'quiz') {
576
            $sql = 'SELECT SUM(ponderation)
577
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
578
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
579
                    ON
580
                        quiz_question.id = quiz_rel_question.question_id AND
581
                        quiz_question.c_id = quiz_rel_question.c_id
582
                    WHERE
583
                        quiz_rel_question.exercice_id = '.$id." AND
584
                        quiz_question.c_id = $course_id AND
585
                        quiz_rel_question.c_id = $course_id ";
586
            $rsQuiz = Database::query($sql);
587
            $max_score = Database::result($rsQuiz, 0, 0);
588
589
            // Disabling the exercise if we add it inside a LP
590
            $exercise = new Exercise($course_id);
591
            $exercise->read($id);
592
            $exercise->disable();
593
            $exercise->save();
594
        } else {
595
            $max_score = 100;
596
        }
597
598
        $params = array(
599
            "c_id" => $course_id,
600
            "lp_id" => $this->get_id(),
601
            "item_type" => $typeCleaned,
602
            "ref" => '',
603
            "title" => $title,
604
            "description" => $description,
605
            "path" => $id,
606
            "max_score" => $max_score,
607
            "parent_item_id" => $parent,
608
            "previous_item_id" => $previous,
609
            "next_item_id" => intval($next),
610
            "display_order" => $display_order + 1,
611
            "prerequisite" => $prerequisites,
612
            "max_time_allowed" => $max_time_allowed,
613
            'min_score' => 0,
614
            'launch_data' => ''
615
        );
616
617
        if ($prerequisites != 0) {
618
            $params['prerequisite'] = $prerequisites;
619
        }
620
621
        $new_item_id = Database::insert($tbl_lp_item, $params);
622
623
        if ($this->debug > 2) {
624
            error_log('New LP - Inserting dir/chapter: '.$new_item_id, 0);
0 ignored issues
show
Bug introduced by
Are you sure $new_item_id of type integer|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

624
            error_log('New LP - Inserting dir/chapter: './** @scrutinizer ignore-type */ $new_item_id, 0);
Loading history...
625
        }
626
627
        if ($new_item_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_item_id of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
628
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
629
            Database::query($sql);
630
631
            $sql = "UPDATE $tbl_lp_item
632
                    SET previous_item_id = $new_item_id 
633
                    WHERE c_id = $course_id AND id = $next";
634
            Database::query($sql);
635
636
            // Update the item that should be before the new item.
637
            $sql = "UPDATE $tbl_lp_item
638
                    SET next_item_id = $new_item_id
639
                    WHERE c_id = $course_id AND id = $tmp_previous";
640
            Database::query($sql);
641
642
            // Update all the items after the new item.
643
            $sql = "UPDATE $tbl_lp_item
644
                        SET display_order = display_order + 1
645
                    WHERE
646
                        c_id = $course_id AND
647
                        lp_id = ".$this->get_id()." AND
648
                        iid <> $new_item_id AND
649
                        parent_item_id = $parent AND
650
                        display_order > $display_order";
651
            Database::query($sql);
652
653
            // Update the item that should come after the new item.
654
            $sql = "UPDATE $tbl_lp_item
655
                    SET ref = $new_item_id
656
                    WHERE c_id = $course_id AND iid = $new_item_id";
657
            Database::query($sql);
658
659
            // Upload audio.
660
            if (!empty($_FILES['mp3']['name'])) {
661
                // Create the audio folder if it does not exist yet.
662
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
663
                if (!is_dir($filepath.'audio')) {
664
                    mkdir(
665
                        $filepath.'audio',
666
                        api_get_permissions_for_new_directories()
667
                    );
668
                    $audio_id = add_document(
669
                        $_course,
670
                        '/audio',
671
                        'folder',
672
                        0,
673
                        'audio',
674
                        '',
675
                        0,
676
                        true,
677
                        null,
678
                        $sessionId,
679
                        $userId
680
                    );
681
                    api_item_property_update(
682
                        $_course,
683
                        TOOL_DOCUMENT,
684
                        $audio_id,
685
                        'FolderCreated',
686
                        $userId,
687
                        null,
688
                        null,
689
                        null,
690
                        null,
691
                        $sessionId
692
                    );
693
                    api_item_property_update(
694
                        $_course,
695
                        TOOL_DOCUMENT,
696
                        $audio_id,
697
                        'invisible',
698
                        $userId,
699
                        null,
700
                        null,
701
                        null,
702
                        null,
703
                        $sessionId
704
                    );
705
                }
706
707
                $file_path = handle_uploaded_document(
708
                    $_course,
709
                    $_FILES['mp3'],
710
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
711
                    '/audio',
712
                    $userId,
713
                    '',
714
                    '',
715
                    '',
716
                    '',
717
                    false
718
                );
719
720
                // Getting the filename only.
721
                $file_components = explode('/', $file_path);
722
                $file = $file_components[count($file_components) - 1];
723
724
                // Store the mp3 file in the lp_item table.
725
                $sql = "UPDATE $tbl_lp_item SET
726
                          audio = '".Database::escape_string($file)."'
727
                        WHERE iid = '" . intval($new_item_id)."'";
728
                Database::query($sql);
729
            }
730
        }
731
732
        return $new_item_id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new_item_id could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
733
    }
734
735
    /**
736
     * Static admin function allowing addition of a learnpath to a course.
737
     * @param string Course code
738
     * @param string Learnpath name
739
     * @param string Learnpath description string, if provided
740
     * @param string Type of learnpath (default = 'guess', others = 'dokeos', 'aicc',...)
741
     * @param string Type of files origin (default = 'zip', others = 'dir','web_dir',...)
742
     * @param string $zipname Zip file containing the learnpath or directory containing the learnpath
743
     * @param string $publicated_on
744
     * @param string $expired_on
745
     * @param int $categoryId
746
     * @param int $userId
747
     * @return integer The new learnpath ID on success, 0 on failure
748
     */
749
    public static function add_lp(
750
        $courseCode,
751
        $name,
752
        $description = '',
753
        $learnpath = 'guess',
754
        $origin = 'zip',
755
        $zipname = '',
756
        $publicated_on = '',
757
        $expired_on = '',
758
        $categoryId = 0,
759
        $userId = 0
760
    ) {
761
        global $charset;
762
763
        if (!empty($courseCode)) {
764
            $courseInfo = api_get_course_info($courseCode);
765
            $course_id = $courseInfo['real_id'];
766
        } else {
767
            $course_id = api_get_course_int_id();
768
            $courseInfo = api_get_course_info();
769
        }
770
771
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
772
        // Check course code exists.
773
        // Check lp_name doesn't exist, otherwise append something.
774
        $i = 0;
775
        $name = Database::escape_string($name);
776
        $categoryId = intval($categoryId);
777
778
        // Session id.
779
        $session_id = api_get_session_id();
780
        $userId = empty($userId) ? api_get_user_id() : $userId;
781
        $check_name = "SELECT * FROM $tbl_lp
782
                       WHERE c_id = $course_id AND name = '$name'";
783
784
        $res_name = Database::query($check_name);
785
786
        if (empty($publicated_on)) {
787
            $publicated_on = null;
788
        } else {
789
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
790
        }
791
792
        if (empty($expired_on)) {
793
            $expired_on = null;
794
        } else {
795
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
796
        }
797
798
        while (Database::num_rows($res_name)) {
799
            // There is already one such name, update the current one a bit.
800
            $i++;
801
            $name = $name.' - '.$i;
802
            $check_name = "SELECT * FROM $tbl_lp 
803
                           WHERE c_id = $course_id AND name = '$name'";
804
            $res_name = Database::query($check_name);
805
        }
806
        // New name does not exist yet; keep it.
807
        // Escape description.
808
        // Kevin: added htmlentities().
809
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
810
        $type = 1;
811
        switch ($learnpath) {
812
            case 'guess':
813
                break;
814
            case 'dokeos':
815
            case 'chamilo':
816
                $type = 1;
817
                break;
818
            case 'aicc':
819
                break;
820
        }
821
822
        switch ($origin) {
823
            case 'zip':
824
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
825
                break;
826
            case 'manual':
827
            default:
828
                $get_max = "SELECT MAX(display_order) 
829
                            FROM $tbl_lp WHERE c_id = $course_id";
830
                $res_max = Database::query($get_max);
831
                if (Database::num_rows($res_max) < 1) {
832
                    $dsp = 1;
833
                } else {
834
                    $row = Database::fetch_array($res_max);
835
                    $dsp = $row[0] + 1;
836
                }
837
838
                $params = [
839
                    'c_id' => $course_id,
840
                    'lp_type' => $type,
841
                    'name' => $name,
842
                    'description' => $description,
843
                    'path' => '',
844
                    'default_view_mod' => 'embedded',
845
                    'default_encoding' => 'UTF-8',
846
                    'display_order' => $dsp,
847
                    'content_maker' => 'Chamilo',
848
                    'content_local' => 'local',
849
                    'js_lib' => '',
850
                    'session_id' => $session_id,
851
                    'created_on' => api_get_utc_datetime(),
852
                    'modified_on'  => api_get_utc_datetime(),
853
                    'publicated_on' => $publicated_on,
854
                    'expired_on' => $expired_on,
855
                    'category_id' => $categoryId,
856
                    'force_commit' => 0,
857
                    'content_license' => '',
858
                    'debug' => 0,
859
                    'theme' => '',
860
                    'preview_image' => '',
861
                    'author' => '',
862
                    'prerequisite' => 0,
863
                    'hide_toc_frame' => 0,
864
                    'seriousgame_mode' => 0,
865
                    'autolaunch' => 0,
866
                    'max_attempts' => 0,
867
                    'subscribe_users' => 0,
868
                    'accumulate_scorm_time' => 1
869
                ];
870
                $id = Database::insert($tbl_lp, $params);
871
872
                if ($id > 0) {
873
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
874
                    Database::query($sql);
875
876
                    // Insert into item_property.
877
                    api_item_property_update(
878
                        $courseInfo,
879
                        TOOL_LEARNPATH,
880
                        $id,
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type false; however, parameter $item_id of api_item_property_update() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

880
                        /** @scrutinizer ignore-type */ $id,
Loading history...
881
                        'LearnpathAdded',
882
                        $userId
883
                    );
884
                    api_set_default_visibility(
885
                        $id,
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type false; however, parameter $item_id of api_set_default_visibility() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

885
                        /** @scrutinizer ignore-type */ $id,
Loading history...
886
                        TOOL_LEARNPATH,
887
                        0,
888
                        $courseInfo,
889
                        $session_id,
890
                        $userId
891
                    );
892
                    return $id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $id could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
893
                }
894
                break;
895
        }
896
    }
897
898
    /**
899
     * Auto completes the parents of an item in case it's been completed or passed
900
     * @param int $item Optional ID of the item from which to look for parents
901
     */
902
    public function autocomplete_parents($item)
903
    {
904
        $debug = $this->debug;
905
906
        if ($debug) {
907
            error_log('Learnpath::autocomplete_parents()');
908
        }
909
910
        if (empty($item)) {
911
            $item = $this->current;
912
        }
913
914
        $currentItem = $this->getItem($item);
915
        if ($currentItem) {
916
            $parent_id = $currentItem->get_parent();
917
            $parent = $this->getItem($parent_id);
918
            if ($parent) {
919
                // if $item points to an object and there is a parent.
920
                if ($debug) {
921
                    error_log(
922
                        'Autocompleting parent of item '.$item.' "'.$currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
923
                        0
924
                    );
925
                }
926
927
                // New experiment including failed and browsed in completed status.
928
                //$current_status = $currentItem->get_status();
929
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
930
                // Fixes chapter auto complete
931
                if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
932
                    // If the current item is completed or passes or succeeded.
933
                    $updateParentStatus = true;
934
                    if ($debug) {
935
                        error_log('Status of current item is alright');
936
                    }
937
938
                    foreach ($parent->get_children() as $childItemId) {
939
                        $childItem = $this->getItem($childItemId);
940
941
                        // If children was not set try to get the info
942
                        if (empty($childItem->db_item_view_id)) {
943
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
944
                        }
945
946
                        // Check all his brothers (parent's children) for completion status.
947
                        if ($childItemId != $item) {
948
                            if ($debug) {
949
                                error_log(
950
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
951
                                    0
952
                                );
953
                            }
954
                            // Trying completing parents of failed and browsed items as well.
955
                            if ($childItem->status_is(
956
                                array(
957
                                    'completed',
958
                                    'passed',
959
                                    'succeeded',
960
                                    'browsed',
961
                                    'failed'
962
                                ))
963
                            ) {
964
                                // Keep completion status to true.
965
                                continue;
966
                            } else {
967
                                if ($debug > 2) {
968
                                    error_log(
969
                                        '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,
970
                                        0
971
                                    );
972
                                }
973
                                $updateParentStatus = false;
974
                                break;
975
                            }
976
                        }
977
                    }
978
979
                    if ($updateParentStatus) {
980
                        // If all the children were completed:
981
                        $parent->set_status('completed');
982
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
983
                        // Force the status to "completed"
984
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
985
                        $this->update_queue[$parent->get_id()] = 'completed';
986
                        if ($debug) {
987
                            error_log(
988
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
989
                                print_r($this->update_queue, 1),
990
                                0
991
                            );
992
                        }
993
                        // Recursive call.
994
                        $this->autocomplete_parents($parent->get_id());
995
                    }
996
                }
997
            } else {
998
                if ($debug) {
999
                    error_log("Parent #$parent_id does not exists");
1000
                }
1001
            }
1002
        } else {
1003
            if ($debug) {
1004
                error_log("#$item is an item that doesn't have parents");
1005
            }
1006
        }
1007
    }
1008
1009
    /**
1010
     * Auto saves the current results into the database for the whole learnpath
1011
     * @todo: Add save operations for the learnpath itself.
1012
     */
1013
    public function autosave()
1014
    {
1015
        if ($this->debug > 0) {
1016
            error_log('New LP - In learnpath::autosave()', 0);
1017
        }
1018
    }
1019
1020
    /**
1021
     * Closes the current resource
1022
     *
1023
     * Stops the timer
1024
     * Saves into the database if required
1025
     * Clears the current resource data from this object
1026
     * @return boolean    True on success, false on failure
1027
     */
1028
    public function close()
1029
    {
1030
        if ($this->debug > 0) {
1031
            error_log('New LP - In learnpath::close()', 0);
1032
        }
1033
        if (empty($this->lp_id)) {
1034
            $this->error = 'Trying to close this learnpath but no ID is set';
1035
            return false;
1036
        }
1037
        $this->current_time_stop = time();
1038
        $this->ordered_items = [];
1039
        $this->index = 0;
1040
        unset($this->lp_id);
1041
        //unset other stuff
1042
        return true;
1043
    }
1044
1045
    /**
1046
     * Static admin function allowing removal of a learnpath
1047
     * @param array $courseInfo
1048
     * @param integer $id    Learnpath ID
1049
     * @param string $delete Whether to delete data or keep it (default: 'keep', others: 'remove')
1050
     * @return boolean    True on success, false on failure (might change that to return number of elements deleted)
1051
     */
1052
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1053
    {
1054
        $course_id = api_get_course_int_id();
1055
        if (!empty($courseInfo)) {
1056
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1057
        }
1058
1059
        // TODO: Implement a way of getting this to work when the current object is not set.
1060
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1061
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1062
        if (!empty($id) && ($id != $this->lp_id)) {
1063
            return false;
1064
        }
1065
1066
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1067
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1068
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1069
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1070
1071
        // Delete lp item id.
1072
        foreach ($this->items as $id => $dummy) {
1073
            $sql = "DELETE FROM $lp_item_view
1074
                    WHERE c_id = $course_id AND lp_item_id = '".$id."'";
1075
            Database::query($sql);
1076
        }
1077
1078
        // Proposed by Christophe (nickname: clefevre)
1079
        $sql = "DELETE FROM $lp_item
1080
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1081
        Database::query($sql);
1082
1083
        $sql = "DELETE FROM $lp_view 
1084
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1085
        Database::query($sql);
1086
1087
        self::toggle_publish($this->lp_id, 'i');
1088
1089
        if ($this->type == 2 || $this->type == 3) {
1090
            // This is a scorm learning path, delete the files as well.
1091
            $sql = "SELECT path FROM $lp
1092
                    WHERE iid = ".$this->lp_id;
1093
            $res = Database::query($sql);
1094
            if (Database::num_rows($res) > 0) {
1095
                $row = Database::fetch_array($res);
1096
                $path = $row['path'];
1097
                $sql = "SELECT id FROM $lp
1098
                        WHERE 
1099
                            c_id = $course_id AND
1100
                            path = '$path' AND 
1101
                            iid != ".$this->lp_id;
1102
                $res = Database::query($sql);
1103
                if (Database::num_rows($res) > 0) {
1104
                    // Another learning path uses this directory, so don't delete it.
1105
                    if ($this->debug > 2) {
1106
                        error_log('New LP - In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1107
                    }
1108
                } else {
1109
                    // No other LP uses that directory, delete it.
1110
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1111
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path for this course.
1112
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1113
                        if ($this->debug > 2) {
1114
                            error_log('New LP - In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1115
                        }
1116
                        // Proposed by Christophe (clefevre).
1117
                        if (strcmp(substr($path, -2), "/.") == 0) {
1118
                            $path = substr($path, 0, -1); // Remove "." at the end.
1119
                        }
1120
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1121
                        rmdirr($course_scorm_dir.$path);
1122
                    }
1123
                }
1124
            }
1125
        }
1126
1127
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1128
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1129
        // Delete tools
1130
        $sql = "DELETE FROM $tbl_tool
1131
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1132
        Database::query($sql);
1133
1134
        $sql = "DELETE FROM $lp 
1135
                WHERE iid = ".$this->lp_id;
1136
        Database::query($sql);
1137
        // Updates the display order of all lps.
1138
        $this->update_display_order();
1139
1140
        api_item_property_update(
1141
            api_get_course_info(),
1142
            TOOL_LEARNPATH,
1143
            $this->lp_id,
1144
            'delete',
1145
            api_get_user_id()
1146
        );
1147
1148
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1149
            api_get_course_id(),
1150
            4,
1151
            $id,
1152
            api_get_session_id()
1153
        );
1154
1155
        if ($link_info !== false) {
1156
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1157
        }
1158
1159
        if (api_get_setting('search_enabled') == 'true') {
1160
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1161
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1162
        }
1163
    }
1164
1165
    /**
1166
     * Removes all the children of one item - dangerous!
1167
     * @param    integer $id Element ID of which children have to be removed
1168
     * @return    integer    Total number of children removed
1169
     */
1170
    public function delete_children_items($id)
1171
    {
1172
        $course_id = $this->course_info['real_id'];
1173
        if ($this->debug > 0) {
1174
            error_log('New LP - In learnpath::delete_children_items('.$id.')', 0);
1175
        }
1176
        $num = 0;
1177
        if (empty($id) || $id != strval(intval($id))) {
1178
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1179
        }
1180
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1181
        $sql = "SELECT * FROM $lp_item 
1182
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1183
        $res = Database::query($sql);
1184
        while ($row = Database::fetch_array($res)) {
1185
            $num += $this->delete_children_items($row['iid']);
1186
            $sql = "DELETE FROM $lp_item 
1187
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1188
            Database::query($sql);
1189
            $num++;
1190
        }
1191
        return $num;
1192
    }
1193
1194
    /**
1195
     * Removes an item from the current learnpath
1196
     * @param    integer $id Elem ID (0 if first)
1197
     * @param    integer $remove Whether to remove the resource/data from the
1198
     * system or leave it (default: 'keep', others 'remove')
1199
     * @return    integer    Number of elements moved
1200
     * @todo implement resource removal
1201
     */
1202
    public function delete_item($id, $remove = 'keep')
1203
    {
1204
        $course_id = api_get_course_int_id();
1205
        if ($this->debug > 0) {
1206
            error_log('New LP - In learnpath::delete_item()', 0);
1207
        }
1208
        // TODO: Implement the resource removal.
1209
        if (empty($id) || $id != strval(intval($id))) {
1210
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1211
        }
1212
        // First select item to get previous, next, and display order.
1213
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1214
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1215
        $res_sel = Database::query($sql_sel);
1216
        if (Database::num_rows($res_sel) < 1) {
1217
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1218
        }
1219
        $row = Database::fetch_array($res_sel);
1220
        $previous = $row['previous_item_id'];
1221
        $next = $row['next_item_id'];
1222
        $display = $row['display_order'];
1223
        $parent = $row['parent_item_id'];
1224
        $lp = $row['lp_id'];
1225
        // Delete children items.
1226
        $num = $this->delete_children_items($id);
1227
        if ($this->debug > 2) {
1228
            error_log('New LP - learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1229
        }
1230
        // Now delete the item.
1231
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1232
        if ($this->debug > 2) {
1233
            error_log('New LP - Deleting item: '.$sql_del, 0);
1234
        }
1235
        Database::query($sql_del);
1236
        // Now update surrounding items.
1237
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1238
                    WHERE iid = $previous";
1239
        Database::query($sql_upd);
1240
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1241
                    WHERE iid = $next";
1242
        Database::query($sql_upd);
1243
        // Now update all following items with new display order.
1244
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1245
                    WHERE 
1246
                        c_id = $course_id AND 
1247
                        lp_id = $lp AND 
1248
                        parent_item_id = $parent AND 
1249
                        display_order > $display";
1250
        Database::query($sql_all);
1251
1252
        //Removing prerequisites since the item will not longer exist
1253
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1254
                    WHERE c_id = $course_id AND prerequisite = $id";
1255
        Database::query($sql_all);
1256
1257
        // Remove from search engine if enabled.
1258
        if (api_get_setting('search_enabled') == 'true') {
1259
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1260
            $sql = 'SELECT * FROM %s 
1261
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1262
                    LIMIT 1';
1263
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1264
            $res = Database::query($sql);
1265
            if (Database::num_rows($res) > 0) {
1266
                $row2 = Database::fetch_array($res);
1267
                $di = new ChamiloIndexer();
1268
                $di->remove_document($row2['search_did']);
1269
            }
1270
            $sql = 'DELETE FROM %s 
1271
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1272
                    LIMIT 1';
1273
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1274
            Database::query($sql);
1275
        }
1276
    }
1277
1278
    /**
1279
     * Updates an item's content in place
1280
     * @param   integer $id Element ID
1281
     * @param   integer $parent Parent item ID
1282
     * @param   integer $previous Previous item ID
1283
     * @param   string  $title Item title
1284
     * @param   string  $description Item description
1285
     * @param   string  $prerequisites Prerequisites (optional)
1286
     * @param   array   $audio The array resulting of the $_FILES[mp3] element
1287
     * @param   int     $max_time_allowed
1288
     * @param   string  $url
1289
     * @return  boolean True on success, false on error
1290
     */
1291
    public function edit_item(
1292
        $id,
1293
        $parent,
1294
        $previous,
1295
        $title,
1296
        $description,
1297
        $prerequisites = '0',
1298
        $audio = [],
1299
        $max_time_allowed = 0,
1300
        $url = ''
1301
    ) {
1302
        $course_id = api_get_course_int_id();
1303
        $_course = api_get_course_info();
1304
1305
        if ($this->debug > 0) {
1306
            error_log('New LP - In learnpath::edit_item()', 0);
1307
        }
1308
        if (empty($max_time_allowed)) {
1309
            $max_time_allowed = 0;
1310
        }
1311
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1312
            return false;
1313
        }
1314
1315
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1316
        $sql = "SELECT * FROM $tbl_lp_item 
1317
                WHERE iid = $id";
1318
        $res_select = Database::query($sql);
1319
        $row_select = Database::fetch_array($res_select);
1320
        $audio_update_sql = '';
1321
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1322
            // Create the audio folder if it does not exist yet.
1323
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1324
            if (!is_dir($filepath.'audio')) {
1325
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1326
                $audio_id = add_document(
1327
                    $_course,
1328
                    '/audio',
1329
                    'folder',
1330
                    0,
1331
                    'audio'
1332
                );
1333
                api_item_property_update(
1334
                    $_course,
1335
                    TOOL_DOCUMENT,
1336
                    $audio_id,
1337
                    'FolderCreated',
1338
                    api_get_user_id(),
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    null,
1343
                    api_get_session_id()
1344
                );
1345
                api_item_property_update(
1346
                    $_course,
1347
                    TOOL_DOCUMENT,
1348
                    $audio_id,
1349
                    'invisible',
1350
                    api_get_user_id(),
1351
                    null,
1352
                    null,
1353
                    null,
1354
                    null,
1355
                    api_get_session_id()
1356
                );
1357
            }
1358
1359
            // Upload file in documents.
1360
            $pi = pathinfo($audio['name']);
1361
            if ($pi['extension'] == 'mp3') {
1362
                $c_det = api_get_course_info($this->cc);
1363
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1364
                $path = handle_uploaded_document(
1365
                    $c_det,
1366
                    $audio,
1367
                    $bp,
1368
                    '/audio',
1369
                    api_get_user_id(),
1370
                    0,
1371
                    null,
1372
                    0,
1373
                    'rename',
1374
                    false,
1375
                    0
1376
                );
1377
                $path = substr($path, 7);
1378
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1379
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1380
            }
1381
        }
1382
1383
        $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false;
1384
        $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false;
1385
1386
        // TODO: htmlspecialchars to be checked for encoding related problems.
1387
        if ($same_parent && $same_previous) {
1388
            // Only update title and description.
1389
            $sql = "UPDATE $tbl_lp_item
1390
                    SET title = '" . Database::escape_string($title)."',
1391
                        prerequisite = '".$prerequisites."',
1392
                        description = '".Database::escape_string($description)."'
1393
                        ".$audio_update_sql.",
1394
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1395
                    WHERE iid = $id";
1396
            Database::query($sql);
1397
        } else {
1398
            $old_parent = $row_select['parent_item_id'];
1399
            $old_previous = $row_select['previous_item_id'];
1400
            $old_next = $row_select['next_item_id'];
1401
            $old_order = $row_select['display_order'];
1402
            $old_prerequisite = $row_select['prerequisite'];
1403
            $old_max_time_allowed = $row_select['max_time_allowed'];
1404
1405
            /* BEGIN -- virtually remove the current item id */
1406
            /* for the next and previous item it is like the current item doesn't exist anymore */
1407
            if ($old_previous != 0) {
1408
                // Next
1409
                $sql = "UPDATE $tbl_lp_item
1410
                        SET next_item_id = $old_next
1411
                        WHERE iid = $old_previous";
1412
                Database::query($sql);
1413
            }
1414
1415
            if ($old_next != 0) {
1416
                // Previous
1417
                $sql = "UPDATE $tbl_lp_item
1418
                        SET previous_item_id = $old_previous
1419
                        WHERE iid = $old_next";
1420
                Database::query($sql);
1421
            }
1422
1423
            // display_order - 1 for every item with a display_order
1424
            // bigger then the display_order of the current item.
1425
            $sql = "UPDATE $tbl_lp_item
1426
                    SET display_order = display_order - 1
1427
                    WHERE
1428
                        c_id = $course_id AND
1429
                        display_order > $old_order AND
1430
                        lp_id = ".$this->lp_id." AND
1431
                        parent_item_id = $old_parent";
1432
            Database::query($sql);
1433
            /* END -- virtually remove the current item id */
1434
1435
            /* BEGIN -- update the current item id to his new location */
1436
            if ($previous == 0) {
1437
                // Select the data of the item that should come after the current item.
1438
                $sql = "SELECT id, display_order
1439
                        FROM $tbl_lp_item
1440
                        WHERE
1441
                            c_id = $course_id AND
1442
                            lp_id = ".$this->lp_id." AND
1443
                            parent_item_id = $parent AND
1444
                            previous_item_id = $previous";
1445
                $res_select_old = Database::query($sql);
1446
                $row_select_old = Database::fetch_array($res_select_old);
1447
1448
                // If the new parent didn't have children before.
1449
                if (Database::num_rows($res_select_old) == 0) {
1450
                    $new_next = 0;
1451
                    $new_order = 1;
1452
                } else {
1453
                    $new_next = $row_select_old['id'];
1454
                    $new_order = $row_select_old['display_order'];
1455
                }
1456
            } else {
1457
                // Select the data of the item that should come before the current item.
1458
                $sql = "SELECT next_item_id, display_order
1459
                        FROM $tbl_lp_item
1460
                        WHERE iid = $previous";
1461
                $res_select_old = Database::query($sql);
1462
                $row_select_old = Database::fetch_array($res_select_old);
1463
                $new_next = $row_select_old['next_item_id'];
1464
                $new_order = $row_select_old['display_order'] + 1;
1465
            }
1466
1467
            // TODO: htmlspecialchars to be checked for encoding related problems.
1468
            // Update the current item with the new data.
1469
            $sql = "UPDATE $tbl_lp_item
1470
                    SET
1471
                        title = '".Database::escape_string($title)."',
1472
                        description = '".Database::escape_string($description)."',
1473
                        parent_item_id = $parent,
1474
                        previous_item_id = $previous,
1475
                        next_item_id = $new_next,
1476
                        display_order = $new_order
1477
                        $audio_update_sql
1478
                    WHERE iid = $id";
1479
            Database::query($sql);
1480
1481
            if ($previous != 0) {
1482
                // Update the previous item's next_item_id.
1483
                $sql = "UPDATE $tbl_lp_item
1484
                        SET next_item_id = $id
1485
                        WHERE iid = $previous";
1486
                Database::query($sql);
1487
            }
1488
1489
            if ($new_next != 0) {
1490
                // Update the next item's previous_item_id.
1491
                $sql = "UPDATE $tbl_lp_item
1492
                        SET previous_item_id = $id
1493
                        WHERE iid = $new_next";
1494
                Database::query($sql);
1495
            }
1496
1497
            if ($old_prerequisite != $prerequisites) {
1498
                $sql = "UPDATE $tbl_lp_item
1499
                        SET prerequisite = '$prerequisites'
1500
                        WHERE iid = $id";
1501
                Database::query($sql);
1502
            }
1503
1504
            if ($old_max_time_allowed != $max_time_allowed) {
1505
                // update max time allowed
1506
                $sql = "UPDATE $tbl_lp_item
1507
                        SET max_time_allowed = $max_time_allowed
1508
                        WHERE iid = $id";
1509
                Database::query($sql);
1510
            }
1511
1512
            // Update all the items with the same or a bigger display_order than the current item.
1513
            $sql = "UPDATE $tbl_lp_item
1514
                    SET display_order = display_order + 1
1515
                    WHERE
1516
                       c_id = $course_id AND
1517
                       lp_id = ".$this->get_id()." AND
1518
                       iid <> $id AND
1519
                       parent_item_id = $parent AND
1520
                       display_order >= $new_order";
1521
            Database::query($sql);
1522
        }
1523
1524
        if ($row_select['item_type'] == 'link') {
1525
            $link = new Link();
1526
            $linkId = $row_select['path'];
1527
            $link->updateLink($linkId, $url);
1528
        }
1529
    }
1530
1531
    /**
1532
     * Updates an item's prereq in place
1533
     * @param    integer $id Element ID
1534
     * @param    string $prerequisite_id Prerequisite Element ID
1535
     * @param    int $mastery_score Prerequisite min score
1536
     * @param    int $max_score Prerequisite max score
1537
     *
1538
     * @return    boolean    True on success, false on error
1539
     */
1540
    public function edit_item_prereq(
1541
        $id,
1542
        $prerequisite_id,
1543
        $mastery_score = 0,
1544
        $max_score = 100
1545
    ) {
1546
        $course_id = api_get_course_int_id();
1547
        if ($this->debug > 0) {
1548
            error_log('New LP - In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1549
        }
1550
1551
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1552
            return false;
1553
        }
1554
1555
        $prerequisite_id = intval($prerequisite_id);
1556
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1557
1558
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1559
            $mastery_score = 0;
1560
        }
1561
1562
        if (!is_numeric($max_score) || $max_score < 0) {
1563
            $max_score = 100;
1564
        }
1565
1566
        /*if ($mastery_score > $max_score) {
1567
            $max_score = $mastery_score;
1568
        }*/
1569
1570
        if (!is_numeric($prerequisite_id)) {
1571
            $prerequisite_id = 'NULL';
1572
        }
1573
1574
        $mastery_score = floatval($mastery_score);
1575
        $max_score = floatval($max_score);
1576
1577
        $sql = " UPDATE $tbl_lp_item
1578
                 SET
1579
                    prerequisite = $prerequisite_id ,
1580
                    prerequisite_min_score = $mastery_score ,
1581
                    prerequisite_max_score = $max_score
1582
                 WHERE iid = $id";
1583
        Database::query($sql);
1584
        // TODO: Update the item object (can be ignored for now because refreshed).
1585
        return true;
1586
    }
1587
1588
    /**
1589
     * Gets all the chapters belonging to the same parent as the item/chapter given
1590
     * Can also be called as abstract method
1591
     * @param    integer $id Item ID
1592
     * @return    array    A list of all the "brother items" (or an empty array on failure)
1593
     */
1594
    public function getSiblingDirectories($id)
1595
    {
1596
        $course_id = api_get_course_int_id();
1597
        if ($this->debug > 0) {
1598
            error_log('New LP - In learnpath::getSiblingDirectories()', 0);
1599
        }
1600
1601
        if (empty($id) || $id != strval(intval($id))) {
1602
            return [];
1603
        }
1604
1605
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1606
        $sql_parent = "SELECT * FROM $lp_item
1607
                       WHERE iid = $id AND item_type='dir'";
1608
        $res_parent = Database::query($sql_parent);
1609
        if (Database::num_rows($res_parent) > 0) {
1610
            $row_parent = Database::fetch_array($res_parent);
1611
            $parent = $row_parent['parent_item_id'];
1612
            $sql = "SELECT * FROM $lp_item
1613
                    WHERE
1614
                        parent_item_id = $parent AND
1615
                        iid = $id AND
1616
                        item_type='dir'
1617
                    ORDER BY display_order";
1618
            $res_bros = Database::query($sql);
1619
1620
            $list = [];
1621
            while ($row_bro = Database::fetch_array($res_bros)) {
1622
                $list[] = $row_bro;
1623
            }
1624
1625
            return $list;
1626
        }
1627
1628
        return [];
1629
    }
1630
1631
    /**
1632
     * Gets all the items belonging to the same parent as the item given
1633
     * Can also be called as abstract method
1634
     * @param    integer $id Item ID
1635
     * @return    array    A list of all the "brother items" (or an empty array on failure)
1636
     */
1637
    public function get_brother_items($id)
1638
    {
1639
        $course_id = api_get_course_int_id();
1640
        if ($this->debug > 0) {
1641
            error_log('New LP - In learnpath::get_brother_items('.$id.')', 0);
1642
        }
1643
1644
        if (empty($id) || $id != strval(intval($id))) {
1645
            return [];
1646
        }
1647
1648
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1649
        $sql_parent = "SELECT * FROM $lp_item 
1650
                       WHERE iid = $id";
1651
        $res_parent = Database::query($sql_parent);
1652
        if (Database::num_rows($res_parent) > 0) {
1653
            $row_parent = Database::fetch_array($res_parent);
1654
            $parent = $row_parent['parent_item_id'];
1655
            $sql = "SELECT * FROM $lp_item 
1656
                    WHERE c_id = $course_id AND parent_item_id = $parent
1657
                    ORDER BY display_order";
1658
            $res_bros = Database::query($sql);
1659
            $list = [];
1660
            while ($row_bro = Database::fetch_array($res_bros)) {
1661
                $list[] = $row_bro;
1662
            }
1663
            return $list;
1664
        }
1665
        return [];
1666
    }
1667
1668
    /**
1669
     * Get the specific prefix index terms of this learning path
1670
     * @param string $prefix
1671
     * @return  array Array of terms
1672
     */
1673
    public function get_common_index_terms_by_prefix($prefix)
1674
    {
1675
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1676
        $terms = get_specific_field_values_list_by_prefix(
1677
            $prefix,
1678
            $this->cc,
1679
            TOOL_LEARNPATH,
1680
            $this->lp_id
1681
        );
1682
        $prefix_terms = [];
1683
        if (!empty($terms)) {
1684
            foreach ($terms as $term) {
1685
                $prefix_terms[] = $term['value'];
1686
            }
1687
        }
1688
        return $prefix_terms;
1689
    }
1690
1691
    /**
1692
     * Gets the number of items currently completed
1693
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1694
     * @return integer The number of items currently completed
1695
     */
1696
    public function get_complete_items_count($failedStatusException = false)
1697
    {
1698
        if ($this->debug > 0) {
1699
            error_log('New LP - In learnpath::get_complete_items_count()', 0);
1700
        }
1701
        $i = 0;
1702
        $completedStatusList = array(
1703
            'completed',
1704
            'passed',
1705
            'succeeded',
1706
            'browsed'
1707
        );
1708
1709
        if (!$failedStatusException) {
1710
            $completedStatusList[] = 'failed';
1711
        }
1712
1713
        foreach ($this->items as $id => $dummy) {
1714
            // Trying failed and browsed considered "progressed" as well.
1715
            if ($this->items[$id]->status_is($completedStatusList) &&
1716
                $this->items[$id]->get_type() != 'dir'
1717
            ) {
1718
                $i++;
1719
            }
1720
        }
1721
        return $i;
1722
    }
1723
1724
    /**
1725
     * Gets the current item ID
1726
     * @return    integer    The current learnpath item id
1727
     */
1728
    public function get_current_item_id()
1729
    {
1730
        $current = 0;
1731
        if ($this->debug > 0) {
1732
            error_log('New LP - In learnpath::get_current_item_id()', 0);
1733
        }
1734
        if (!empty($this->current)) {
1735
            $current = $this->current;
1736
        }
1737
        if ($this->debug > 2) {
1738
            error_log('New LP - In learnpath::get_current_item_id() - Returning '.$current, 0);
1739
        }
1740
        return $current;
1741
    }
1742
1743
    /**
1744
     * Force to get the first learnpath item id
1745
     * @return    integer    The current learnpath item id
1746
     */
1747
    public function get_first_item_id()
1748
    {
1749
        $current = 0;
1750
        if (is_array($this->ordered_items)) {
1751
            $current = $this->ordered_items[0];
1752
        }
1753
        return $current;
1754
    }
1755
1756
    /**
1757
     * Gets the total number of items available for viewing in this SCORM
1758
     * @return    integer    The total number of items
1759
     */
1760
    public function get_total_items_count()
1761
    {
1762
        if ($this->debug > 0) {
1763
            error_log('New LP - In learnpath::get_total_items_count()', 0);
1764
        }
1765
        return count($this->items);
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type mixed; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1765
        return count(/** @scrutinizer ignore-type */ $this->items);
Loading history...
1766
    }
1767
1768
    /**
1769
     * Gets the total number of items available for viewing in this SCORM but without chapters
1770
     * @return    integer    The total no-chapters number of items
1771
     */
1772
    public function getTotalItemsCountWithoutDirs()
1773
    {
1774
        if ($this->debug > 0) {
1775
            error_log('New LP - In learnpath::getTotalItemsCountWithoutDirs()', 0);
1776
        }
1777
        $total = 0;
1778
        $typeListNotToCount = self::getChapterTypes();
1779
        foreach ($this->items as $temp2) {
1780
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1781
                $total++;
1782
            }
1783
        }
1784
        return $total;
1785
    }
1786
1787
    /**
1788
    *  Sets the first element URL.
1789
     */
1790
    public function first()
1791
    {
1792
        if ($this->debug > 0) {
1793
            error_log('New LP - In learnpath::first()', 0);
1794
            error_log('$this->last_item_seen '.$this->last_item_seen);
1795
        }
1796
1797
        // Test if the last_item_seen exists and is not a dir.
1798
        if (count($this->ordered_items) == 0) {
1799
            $this->index = 0;
1800
        }
1801
1802
        if ($this->debug > 0) {
1803
            if (isset($this->items[$this->last_item_seen])) {
1804
                $status = $this->items[$this->last_item_seen]->get_status();
1805
            }
1806
        }
1807
1808
        if (!empty($this->last_item_seen) &&
1809
            !empty($this->items[$this->last_item_seen]) &&
1810
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1811
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1812
            //&& !$this->items[$this->last_item_seen]->is_done()
1813
        ) {
1814
            if ($this->debug > 2) {
1815
                error_log('New LP - In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.$this->items[$this->last_item_seen]->get_type(), 0);
1816
            }
1817
            $index = -1;
1818
            foreach ($this->ordered_items as $myindex => $item_id) {
1819
                if ($item_id == $this->last_item_seen) {
1820
                    $index = $myindex;
1821
                    break;
1822
                }
1823
            }
1824
            if ($index == -1) {
1825
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1826
                if ($this->debug > 2) {
1827
                    error_log('New LP - Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1828
                }
1829
                return false;
1830
            } else {
1831
                $this->last     = $this->last_item_seen;
1832
                $this->current  = $this->last_item_seen;
1833
                $this->index    = $index;
1834
            }
1835
        } else {
1836
            if ($this->debug > 2) {
1837
                error_log('New LP - In learnpath::first() - No last item seen', 0);
1838
            }
1839
            $index = 0;
1840
            // Loop through all ordered items and stop at the first item that is
1841
            // not a directory *and* that has not been completed yet.
1842
            while (!empty($this->ordered_items[$index]) &&
1843
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1844
                (
1845
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1846
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1847
                ) && $index < $this->max_ordered_items) {
1848
                $index++;
1849
            }
1850
            $this->last = $this->current;
1851
            // current is
1852
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1853
            $this->index = $index;
1854
            if ($this->debug > 2) {
1855
                error_log('$index '.$index);
1856
                error_log('New LP - In learnpath::first() - No last item seen. New last = '.$this->last.'('.$this->ordered_items[$index].')', 0);
1857
            }
1858
        }
1859
        if ($this->debug > 2) {
1860
            error_log('New LP - In learnpath::first() - First item is '.$this->get_current_item_id());
1861
        }
1862
    }
1863
1864
    /**
1865
     * Gets the information about an item in a format usable as JavaScript to update
1866
     * the JS API by just printing this content into the <head> section of the message frame
1867
     * @param   int $item_id
1868
     * @return  string
1869
     */
1870
    public function get_js_info($item_id = 0)
1871
    {
1872
        if ($this->debug > 0) {
1873
            error_log('New LP - In learnpath::get_js_info('.$item_id.')', 0);
1874
        }
1875
1876
        $info = '';
1877
        $item_id = intval($item_id);
1878
1879
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1880
            //if item is defined, return values from DB
1881
            $oItem = $this->items[$item_id];
1882
            $info .= '<script language="javascript">';
1883
            $info .= "top.set_score(".$oItem->get_score().");\n";
1884
            $info .= "top.set_max(".$oItem->get_max().");\n";
1885
            $info .= "top.set_min(".$oItem->get_min().");\n";
1886
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1887
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1888
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1889
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1890
            $info .= "top.set_flag_synchronized();";
1891
            $info .= '</script>';
1892
            if ($this->debug > 2) {
1893
                error_log('New LP - in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1894
            }
1895
            return $info;
1896
1897
        } else {
1898
            // If item_id is empty, just update to default SCORM data.
1899
            $info .= '<script language="javascript">';
1900
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_score() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1900
            $info .= "top.set_score(".learnpathItem::/** @scrutinizer ignore-call */ get_score().");\n";
Loading history...
1901
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_max() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1901
            $info .= "top.set_max(".learnpathItem::/** @scrutinizer ignore-call */ get_max().");\n";
Loading history...
1902
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_min() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1902
            $info .= "top.set_min(".learnpathItem::/** @scrutinizer ignore-call */ get_min().");\n";
Loading history...
1903
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_status() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1903
            $info .= "top.set_lesson_status('".learnpathItem::/** @scrutinizer ignore-call */ get_status()."');";
Loading history...
1904
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1905
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_suspend_data() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1905
            $info .= "top.set_suspend_data('".learnpathItem::/** @scrutinizer ignore-call */ get_suspend_data()."');";
Loading history...
1906
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1907
            $info .= "top.set_flag_synchronized();";
1908
            $info .= '</script>';
1909
            if ($this->debug > 2) {
1910
                error_log('New LP - in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1911
            }
1912
            return $info;
1913
        }
1914
    }
1915
1916
    /**
1917
     * Gets the js library from the database
1918
     * @return    string    The name of the javascript library to be used
1919
     */
1920
    public function get_js_lib()
1921
    {
1922
        $lib = '';
1923
        if (!empty($this->js_lib)) {
1924
            $lib = $this->js_lib;
1925
        }
1926
        return $lib;
1927
    }
1928
1929
    /**
1930
     * Gets the learnpath database ID
1931
     * @return	integer	Learnpath ID in the lp table
1932
     */
1933
    public function get_id()
1934
    {
1935
        if (!empty($this->lp_id)) {
1936
            return $this->lp_id;
1937
        } else {
1938
            return 0;
1939
        }
1940
    }
1941
1942
    /**
1943
     * Gets the last element URL.
1944
     * @return string URL to load into the viewer
1945
     */
1946
    public function get_last()
1947
    {
1948
        if ($this->debug > 0) {
1949
            error_log('New LP - In learnpath::get_last()', 0);
1950
        }
1951
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1952
        if (count($this->ordered_items) > 0) {
1953
            $this->index = count($this->ordered_items) - 1;
1954
            return $this->ordered_items[$this->index];
1955
        }
1956
1957
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1958
    }
1959
1960
    /**
1961
     * Gets the navigation bar for the learnpath display screen
1962
     * @return	string	The HTML string to use as a navigation bar
1963
     */
1964
    public function get_navigation_bar($idBar = null, $display = null)
1965
    {
1966
        if ($this->debug > 0) {
1967
            error_log('New LP - In learnpath::get_navigation_bar()', 0);
1968
        }
1969
        if (empty($idBar)) {
1970
            $idBar = 'control-top';
1971
        }
1972
1973
        $navbar = null;
1974
        $lp_id = $this->lp_id;
1975
        $mycurrentitemid = $this->get_current_item_id();
1976
1977
        $reportingText = get_lang('Reporting');
1978
        $previousText = get_lang('ScormPrevious');
1979
        $nextText = get_lang('ScormNext');
1980
        $fullScreenText = get_lang('ScormExitFullScreen');
1981
1982
        if ($this->mode == 'fullscreen') {
1983
            $navbar = '
1984
                  <span id="'.$idBar.'" class="buttons">
1985
                    <a class="icon-toolbar" href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lp_id.'" onclick="window.parent.API.save_asset();return true;" target="content_name" title="'.$reportingText.'" id="stats_link">
1986
                        <span class="fa fa-info"></span><span class="sr-only">' . $reportingText.'</span>
1987
                    </a>
1988
                    <a class="icon-toolbar" id="scorm-previous" href="#" onclick="switch_item(' . $mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1989
                        <span class="fa fa-chevron-left"></span><span class="sr-only">' . $previousText.'</span>
1990
                    </a>
1991
                    <a class="icon-toolbar" id="scorm-next" href="#" onclick="switch_item(' . $mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1992
                        <span class="fa fa-chevron-right"></span><span class="sr-only">' . $nextText.'</span>
1993
                    </a>
1994
                    <a class="icon-toolbar" id="view-embedded" href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1995
                        <span class="fa fa-columns"></span><span class="sr-only">' . $fullScreenText.'</span>
1996
                    </a>
1997
                  </span>';
1998
        } else {
1999
            $navbar = '
2000
            <span id="'.$idBar.'" class="buttons text-right">
2001
                <a class="icon-toolbar" href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lp_id.'" onclick="window.parent.API.save_asset();return true;" target="content_name" title="'.$reportingText.'" id="stats_link">
2002
                    <span class="fa fa-info"></span><span class="sr-only">' . $reportingText.'</span>
2003
                </a>
2004
                <a class="icon-toolbar" id="scorm-previous" href="#" onclick="switch_item(' . $mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2005
                    <span class="fa fa-chevron-left"></span><span class="sr-only">' . $previousText.'</span>
2006
                </a>
2007
                <a class="icon-toolbar" id="scorm-next" href="#" onclick="switch_item(' . $mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2008
                    <span class="fa fa-chevron-right"></span><span class="sr-only">' . $nextText.'</span>
2009
                </a>
2010
            </span>';
2011
        }
2012
2013
        return $navbar;
2014
    }
2015
2016
    /**
2017
     * Gets the next resource in queue (url).
2018
     * @return	string	URL to load into the viewer
2019
     */
2020
    public function get_next_index()
2021
    {
2022
        if ($this->debug > 0) {
2023
            error_log('New LP - In learnpath::get_next_index()', 0);
2024
        }
2025
        // TODO
2026
        $index = $this->index;
2027
        $index++;
2028
        if ($this->debug > 2) {
2029
            error_log('New LP - Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2030
        }
2031
        while (
2032
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2033
            $index < $this->max_ordered_items
2034
        ) {
2035
            $index++;
2036
            if ($index == $this->max_ordered_items) {
2037
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2038
                    return $this->index;
2039
                } else {
2040
                    return $index;
2041
                }
2042
            }
2043
        }
2044
        if (empty ($this->ordered_items[$index])) {
2045
            return $this->index;
2046
        }
2047
        if ($this->debug > 2) {
2048
            error_log('New LP - index is now '.$index, 0);
2049
        }
2050
        return $index;
2051
    }
2052
2053
    /**
2054
     * Gets item_id for the next element
2055
     * @return	integer	Next item (DB) ID
2056
     */
2057
    public function get_next_item_id()
2058
    {
2059
        if ($this->debug > 0) {
2060
            error_log('New LP - In learnpath::get_next_item_id()', 0);
2061
        }
2062
        $new_index = $this->get_next_index();
2063
        if (!empty ($new_index)) {
2064
            if (isset ($this->ordered_items[$new_index])) {
2065
                if ($this->debug > 2) {
2066
                    error_log('New LP - In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2067
                }
2068
                return $this->ordered_items[$new_index];
2069
            }
2070
        }
2071
        if ($this->debug > 2) {
2072
            error_log('New LP - In learnpath::get_next_index() - Problem - Returning 0', 0);
2073
        }
2074
        return 0;
2075
    }
2076
2077
    /**
2078
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...)
2079
     *
2080
     * Generally, the package provided is in the form of a zip file, so the function
2081
     * has been written to test a zip file. If not a zip, the function will return the
2082
     * default return value: ''
2083
     * @param	string	the path to the file
2084
     * @param	string 	the original name of the file
2085
     * @return	string	'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2086
     */
2087
    public static function get_package_type($file_path, $file_name)
2088
    {
2089
        // Get name of the zip file without the extension.
2090
        $file_info = pathinfo($file_name);
2091
        $filename = $file_info['basename']; // Name including extension.
2092
        $extension = $file_info['extension']; // Extension only.
2093
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), array(
2094
                'dll',
2095
                'exe'
2096
            ))) {
2097
            return 'oogie';
2098
        }
2099
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), array(
2100
                'dll',
2101
                'exe'
2102
            ))) {
2103
            return 'woogie';
2104
        }
2105
2106
        $zipFile = new PclZip($file_path);
2107
        // Check the zip content (real size and file extension).
2108
        $zipContentArray = $zipFile->listContent();
2109
        $package_type = '';
2110
        $at_root = false;
2111
        $manifest = '';
2112
        $aicc_match_crs = 0;
2113
        $aicc_match_au = 0;
2114
        $aicc_match_des = 0;
2115
        $aicc_match_cst = 0;
2116
2117
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2118
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2119
            foreach ($zipContentArray as $thisContent) {
2120
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2121
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2122
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2123
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2124
                    $package_type = 'scorm';
2125
                    break; // Exit the foreach loop.
2126
                } elseif (
2127
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2128
                    in_array(
2129
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2130
                        array('crs', 'au', 'des', 'cst')
2131
                    )
2132
                ) {
2133
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2134
                    switch ($ext) {
2135
                        case 'crs':
2136
                            $aicc_match_crs = 1;
2137
                            break;
2138
                        case 'au':
2139
                            $aicc_match_au = 1;
2140
                            break;
2141
                        case 'des':
2142
                            $aicc_match_des = 1;
2143
                            break;
2144
                        case 'cst':
2145
                            $aicc_match_cst = 1;
2146
                            break;
2147
                        default:
2148
                            break;
2149
                    }
2150
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2151
                } else {
2152
                    $package_type = '';
2153
                }
2154
            }
2155
        }
2156
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2157
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2158
            $package_type = 'aicc';
2159
        }
2160
        return $package_type;
2161
    }
2162
2163
    /**
2164
     * Gets the previous resource in queue (url). Also initialises time values for this viewing
2165
     * @return string URL to load into the viewer
2166
     */
2167
    public function get_previous_index()
2168
    {
2169
        if ($this->debug > 0) {
2170
            error_log('New LP - In learnpath::get_previous_index()', 0);
2171
        }
2172
        $index = $this->index;
2173
        if (isset ($this->ordered_items[$index - 1])) {
2174
            $index--;
2175
            while (isset($this->ordered_items[$index]) &&
2176
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2177
            ) {
2178
                $index--;
2179
                if ($index < 0) {
2180
                    return $this->index;
2181
                }
2182
            }
2183
        } else {
2184
            if ($this->debug > 2) {
2185
                error_log('New LP - get_previous_index() - there was no previous index available, reusing '.$index, 0);
2186
            }
2187
            // There is no previous item.
2188
        }
2189
        return $index;
2190
    }
2191
2192
    /**
2193
     * Gets item_id for the next element
2194
     * @return	integer	Previous item (DB) ID
2195
     */
2196
    public function get_previous_item_id()
2197
    {
2198
        if ($this->debug > 0) {
2199
            error_log('New LP - In learnpath::get_previous_item_id()', 0);
2200
        }
2201
        $new_index = $this->get_previous_index();
2202
        return $this->ordered_items[$new_index];
2203
    }
2204
2205
    /**
2206
     * Gets the progress value from the progress_db attribute
2207
     * @return	integer	Current progress value
2208
     * @deprecated This method does not seem to be used as of 20170514
2209
     */
2210
    public function get_progress()
2211
    {
2212
        if ($this->debug > 0) {
2213
            error_log('New LP - In learnpath::get_progress()', 0);
2214
        }
2215
        if (!empty ($this->progress_db)) {
2216
            return $this->progress_db;
2217
        }
2218
        return 0;
2219
    }
2220
2221
    /**
2222
     * Returns the HTML necessary to print a mediaplayer block inside a page
2223
     * @param int $lpItemId
2224
     * @param string $autostart
2225
     * @return string	The mediaplayer HTML
2226
     */
2227
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2228
    {
2229
        $course_id = api_get_course_int_id();
2230
        $_course = api_get_course_info();
2231
        if (empty($_course)) {
2232
            return '';
2233
        }
2234
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2235
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2236
2237
        // Getting all the information about the item.
2238
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2239
                INNER JOIN $tbl_lp_item_view as lp_view
2240
                ON (lpi.iid = lp_view.lp_item_id)
2241
                WHERE
2242
                    lpi.iid = $lpItemId AND
2243
                    lp_view.c_id = $course_id";
2244
        $result = Database::query($sql);
2245
        $row = Database::fetch_assoc($result);
2246
        $output = '';
2247
2248
        if (!empty ($row['audio'])) {
2249
            $list = $_SESSION['oLP']->get_toc();
2250
            $type_quiz = false;
2251
2252
            foreach ($list as $toc) {
2253
                if ($toc['id'] == $_SESSION['oLP']->current && ($toc['type'] == 'quiz')) {
2254
                    $type_quiz = true;
2255
                }
2256
            }
2257
2258
            if ($type_quiz) {
2259
                if ($_SESSION['oLP']->prevent_reinit == 1) {
2260
                    $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2261
                } else {
2262
                    $autostart_audio = $autostart;
2263
                }
2264
            } else {
2265
                $autostart_audio = 'true';
2266
            }
2267
2268
            $courseInfo = api_get_course_info();
2269
            $audio = $row['audio'];
2270
2271
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2272
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2273
2274
            if (!file_exists($file)) {
2275
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2276
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2277
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2278
            }
2279
2280
            $player = Display::getMediaPlayer(
2281
                $file,
2282
                array(
2283
                    'id' => 'lp_audio_media_player',
2284
                    'url' => $url,
2285
                    'autoplay' => $autostart_audio,
2286
                    'width' => '100%'
2287
                )
2288
            );
2289
2290
            // The mp3 player.
2291
            $output  = '<div id="container">';
2292
            $output .= $player;
2293
            $output .= '</div>';
2294
        }
2295
2296
        return $output;
2297
    }
2298
2299
    /**
2300
     * @param int $studentId
2301
     * @param int $prerequisite
2302
     * @param array $courseInfo
2303
     * @param int $sessionId
2304
     *
2305
     * @return bool
2306
     *
2307
     */
2308
    public static function isBlockedByPrerequisite(
2309
        $studentId,
2310
        $prerequisite,
2311
        $courseInfo,
2312
        $sessionId
2313
    ) {
2314
        $isBlocked = false;
2315
2316
        if (!empty($prerequisite)) {
2317
            $progress = self::getProgress(
2318
                $prerequisite,
2319
                $studentId,
2320
                $courseInfo['real_id'],
2321
                $sessionId
2322
            );
2323
            if ($progress < 100) {
2324
                $isBlocked = true;
2325
            }
2326
        }
2327
2328
        return $isBlocked;
2329
    }
2330
2331
    /**
2332
     * Checks if the learning path is visible for student after the progress
2333
     * of its prerequisite is completed, considering the time availability and
2334
     * the LP visibility.
2335
     * @param int $lp_id
2336
     * @param int $student_id
2337
     * @param string Course code (optional)
2338
     * @param int $sessionId
2339
     * @return	bool
2340
     */
2341
    public static function is_lp_visible_for_student(
2342
        $lp_id,
2343
        $student_id,
2344
        $courseCode = null,
2345
        $sessionId = 0
2346
    ) {
2347
        $courseInfo = api_get_course_info($courseCode);
2348
        $lp_id = (int) $lp_id;
2349
        $sessionId = (int) $sessionId;
2350
2351
        if (empty($courseInfo)) {
2352
            return false;
2353
        }
2354
2355
        if (empty($sessionId)) {
2356
            $sessionId = api_get_session_id();
2357
        }
2358
2359
        $itemInfo = api_get_item_property_info(
2360
            $courseInfo['real_id'],
2361
            TOOL_LEARNPATH,
2362
            $lp_id,
2363
            $sessionId
2364
        );
2365
2366
        // If the item was deleted.
2367
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2368
            return false;
2369
        }
2370
2371
        // @todo remove this query and load the row info as a parameter
2372
        $tbl_learnpath = Database::get_course_table(TABLE_LP_MAIN);
2373
        // Get current prerequisite
2374
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2375
                FROM $tbl_learnpath
2376
                WHERE iid = $lp_id";
2377
        $rs  = Database::query($sql);
2378
        $now = time();
2379
        if (Database::num_rows($rs) > 0) {
2380
            $row = Database::fetch_array($rs, 'ASSOC');
2381
            $prerequisite = $row['prerequisite'];
2382
            $is_visible = true;
2383
2384
            $isBlocked = self::isBlockedByPrerequisite(
2385
                $student_id,
2386
                $prerequisite,
2387
                $courseInfo,
2388
                $sessionId
2389
            );
2390
2391
            if ($isBlocked) {
2392
                $is_visible = false;
2393
            }
2394
2395
            // Also check the time availability of the LP
2396
            if ($is_visible) {
2397
                // Adding visibility restrictions
2398
                if (!empty($row['publicated_on'])) {
2399
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2400
                        $is_visible = false;
2401
                    }
2402
                }
2403
                // Blocking empty start times see BT#2800
2404
                global $_custom;
2405
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2406
                    $_custom['lps_hidden_when_no_start_date']
2407
                ) {
2408
                    if (empty($row['publicated_on'])) {
2409
                        $is_visible = false;
2410
                    }
2411
                }
2412
2413
                if (!empty($row['expired_on'])) {
2414
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2415
                        $is_visible = false;
2416
                    }
2417
                }
2418
            }
2419
2420
            // Check if the subscription users/group to a LP is ON
2421
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1) {
2422
                // Try group
2423
                $is_visible = false;
2424
                // Checking only the user visibility
2425
                $userVisibility = api_get_item_visibility(
2426
                    $courseInfo,
2427
                    'learnpath',
2428
                    $row['id'],
2429
                    $sessionId,
2430
                    $student_id,
2431
                    'LearnpathSubscription'
2432
                );
2433
2434
                if ($userVisibility == 1) {
2435
                    $is_visible = true;
2436
                } else {
2437
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2438
                    if (!empty($userGroups)) {
2439
                        foreach ($userGroups as $groupInfo) {
2440
                            $groupId = $groupInfo['iid'];
2441
                            $userVisibility = api_get_item_visibility(
2442
                                $courseInfo,
2443
                                'learnpath',
2444
                                $row['id'],
2445
                                $sessionId,
2446
                                null,
2447
                                'LearnpathSubscription',
2448
                                $groupId
2449
                            );
2450
2451
                            if ($userVisibility == 1) {
2452
                                $is_visible = true;
2453
                                break;
2454
                            }
2455
                        }
2456
                    }
2457
                }
2458
            }
2459
2460
            return $is_visible;
2461
        }
2462
2463
        return false;
2464
    }
2465
2466
    /**
2467
     * @param int $lpId
2468
     * @param int $userId
2469
     * @param int $courseId
2470
     * @param int $sessionId
2471
     * @return int
2472
     */
2473
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2474
    {
2475
        $lpId = intval($lpId);
2476
        $userId = intval($userId);
2477
        $courseId = intval($courseId);
2478
        $sessionId = intval($sessionId);
2479
        $progress = 0;
2480
2481
        $sessionCondition = api_get_session_condition($sessionId);
2482
        $table = Database::get_course_table(TABLE_LP_VIEW);
2483
        $sql = "SELECT * FROM $table
2484
                WHERE
2485
                    c_id = $courseId AND
2486
                    lp_id = $lpId AND
2487
                    user_id = $userId $sessionCondition ";
2488
        $res = Database::query($sql);
2489
        if (Database::num_rows($res) > 0) {
2490
            $row = Database:: fetch_array($res);
2491
            $progress = $row['progress'];
2492
        }
2493
2494
        return (int) $progress;
2495
    }
2496
2497
    /**
2498
     * Displays a progress bar
2499
     * completed so far.
2500
     * @param	integer	$percentage Progress value to display
2501
     * @param	string	$text_add Text to display near the progress value
2502
     * @return	string	HTML string containing the progress bar
2503
     */
2504
    public static function get_progress_bar($percentage = -1, $text_add = '')
2505
    {
2506
        $text = $percentage.$text_add;
2507
        $output = '<div class="progress">
2508
            <div id="progress_bar_value" 
2509
                class="progress-bar progress-bar-success" role="progressbar" 
2510
                aria-valuenow="' .$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2511
            '. $text.'
2512
            </div>
2513
        </div>';
2514
2515
        return $output;
2516
    }
2517
2518
    /**
2519
     * @param string $mode can be '%' or 'abs'
2520
     * otherwise this value will be used $this->progress_bar_mode
2521
     * @return string
2522
     */
2523
    public function getProgressBar($mode = null)
2524
    {
2525
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2526
        return self::get_progress_bar($percentage, $text_add);
2527
    }
2528
2529
    /**
2530
     * Gets the progress bar info to display inside the progress bar.
2531
     * Also used by scorm_api.php
2532
     * @param	string	$mode Mode of display (can be '%' or 'abs').abs means
2533
     * we display a number of completed elements per total elements
2534
     * @param	integer	$add Additional steps to fake as completed
2535
     * @return array Percentage or number and symbol (% or /xx)
2536
     */
2537
    public function get_progress_bar_text($mode = '', $add = 0)
2538
    {
2539
        if ($this->debug > 0) {
2540
            error_log('New LP - In learnpath::get_progress_bar_text()', 0);
2541
        }
2542
        if (empty($mode)) {
2543
            $mode = $this->progress_bar_mode;
2544
        }
2545
        $total_items = $this->getTotalItemsCountWithoutDirs();
2546
        if ($this->debug > 2) {
2547
            error_log('New LP - Total items available in this learnpath: '.$total_items, 0);
2548
        }
2549
        $completeItems = $this->get_complete_items_count();
2550
        if ($this->debug > 2) {
2551
            error_log('New LP - Items completed so far: '.$completeItems, 0);
2552
        }
2553
        if ($add != 0) {
2554
            $completeItems += $add;
2555
            if ($this->debug > 2) {
2556
                error_log('New LP - Items completed so far (+modifier): '.$completeItems, 0);
2557
            }
2558
        }
2559
        $text = '';
2560
        if ($completeItems > $total_items) {
2561
            $completeItems = $total_items;
2562
        }
2563
        $percentage = 0;
2564
        if ($mode == '%') {
2565
            if ($total_items > 0) {
2566
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2567
            } else {
2568
                $percentage = 0;
2569
            }
2570
            $percentage = number_format($percentage, 0);
2571
            $text = '%';
2572
        } elseif ($mode == 'abs') {
2573
            $percentage = $completeItems;
2574
            $text = '/'.$total_items;
2575
        }
2576
2577
        return array(
2578
            $percentage,
2579
            $text
2580
        );
2581
    }
2582
2583
    /**
2584
     * Gets the progress bar mode
2585
     * @return	string	The progress bar mode attribute
2586
     */
2587
    public function get_progress_bar_mode()
2588
    {
2589
        if ($this->debug > 0) {
2590
            error_log('New LP - In learnpath::get_progress_bar_mode()', 0);
2591
        }
2592
        if (!empty($this->progress_bar_mode)) {
2593
            return $this->progress_bar_mode;
2594
        } else {
2595
            return '%';
2596
        }
2597
    }
2598
2599
    /**
2600
     * Gets the learnpath proximity (remote or local)
2601
     * @return	string	Learnpath proximity
2602
     */
2603
    public function get_proximity()
2604
    {
2605
        if ($this->debug > 0) {
2606
            error_log('New LP - In learnpath::get_proximity()', 0);
2607
        }
2608
        if (!empty($this->proximity)) {
2609
            return $this->proximity;
2610
        } else {
2611
            return '';
2612
        }
2613
    }
2614
2615
    /**
2616
     * Gets the learnpath theme (remote or local)
2617
     * @return	string	Learnpath theme
2618
     */
2619
    public function get_theme()
2620
    {
2621
        if ($this->debug > 0) {
2622
            error_log('New LP - In learnpath::get_theme()', 0);
2623
        }
2624
        if (!empty ($this->theme)) {
2625
            return $this->theme;
2626
        } else {
2627
            return '';
2628
        }
2629
    }
2630
2631
    /**
2632
     * Gets the learnpath session id
2633
     * @return int
2634
     */
2635
    public function get_lp_session_id()
2636
    {
2637
        if ($this->debug > 0) {
2638
            error_log('New LP - In learnpath::get_lp_session_id()', 0);
2639
        }
2640
        if (!empty($this->lp_session_id)) {
2641
            return (int) $this->lp_session_id;
2642
        } else {
2643
            return 0;
2644
        }
2645
    }
2646
2647
    /**
2648
     * Gets the learnpath image
2649
     * @return	string	Web URL of the LP image
2650
     */
2651
    public function get_preview_image()
2652
    {
2653
        if ($this->debug > 0) {
2654
            error_log('New LP - In learnpath::get_preview_image()', 0);
2655
        }
2656
        if (!empty($this->preview_image)) {
2657
            return $this->preview_image;
2658
        } else {
2659
            return '';
2660
        }
2661
    }
2662
2663
    /**
2664
     * @param string $size
2665
     * @param string $path_type
2666
     * @return bool|string
2667
     */
2668
    public function get_preview_image_path($size = null, $path_type = 'web')
2669
    {
2670
        $preview_image = $this->get_preview_image();
2671
        if (isset($preview_image) && !empty($preview_image)) {
2672
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2673
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2674
2675
            if (isset($size)) {
2676
                $info = pathinfo($preview_image);
2677
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2678
2679
                if (file_exists($image_sys_path.$image_custom_size)) {
2680
                    if ($path_type == 'web') {
2681
                        return $image_path.$image_custom_size;
2682
                    } else {
2683
                        return $image_sys_path.$image_custom_size;
2684
                    }
2685
                }
2686
            } else {
2687
                if ($path_type == 'web') {
2688
                    return $image_path.$preview_image;
2689
                } else {
2690
                    return $image_sys_path.$preview_image;
2691
                }
2692
            }
2693
        }
2694
2695
        return false;
2696
    }
2697
2698
    /**
2699
     * Gets the learnpath author
2700
     * @return string	LP's author
2701
     */
2702
    public function get_author()
2703
    {
2704
        if ($this->debug > 0) {
2705
            error_log('New LP - In learnpath::get_author()', 0);
2706
        }
2707
        if (!empty ($this->author)) {
2708
            return $this->author;
2709
        } else {
2710
            return '';
2711
        }
2712
    }
2713
2714
    /**
2715
     * Gets hide table of contents
2716
     * @return int
2717
     */
2718
    public function getHideTableOfContents()
2719
    {
2720
        return (int) $this->hide_toc_frame;
2721
    }
2722
2723
    /**
2724
     * Generate a new prerequisites string for a given item. If this item was a sco and
2725
     * its prerequisites were strings (instead of IDs), then transform those strings into
2726
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2727
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2728
     * same rule as the scorm_export() method
2729
     * @param	integer		Item ID
2730
     * @return	string		Prerequisites string ready for the export as SCORM
2731
     */
2732
    public function get_scorm_prereq_string($item_id)
2733
    {
2734
        if ($this->debug > 0) {
2735
            error_log('New LP - In learnpath::get_scorm_prereq_string()');
2736
        }
2737
        if (!is_object($this->items[$item_id])) {
2738
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2739
        }
2740
        /** @var learnpathItem $oItem */
2741
        $oItem = $this->items[$item_id];
2742
        $prereq = $oItem->get_prereq_string();
2743
2744
        if (empty($prereq)) {
2745
            return '';
2746
        }
2747
        if (preg_match('/^\d+$/', $prereq) && is_object($this->items[$prereq])) {
2748
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2749
            // then simply return it (with the ITEM_ prefix).
2750
            //return 'ITEM_' . $prereq;
2751
            return $this->items[$prereq]->ref;
2752
        } else {
2753
            if (isset($this->refs_list[$prereq])) {
2754
                // It's a simple string item from which the ID can be found in the refs list,
2755
                // so we can transform it directly to an ID for export.
2756
                return $this->items[$this->refs_list[$prereq]]->ref;
2757
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2758
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2759
            } else {
2760
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2761
                // and replace them, one by one, by the internal IDs (chamilo db)
2762
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2763
                // by a space as well.
2764
                $find = array(
2765
                    '&',
2766
                    '|',
2767
                    '~',
2768
                    '=',
2769
                    '<>',
2770
                    '{',
2771
                    '}',
2772
                    '*',
2773
                    '(',
2774
                    ')'
2775
                );
2776
                $replace = array(
2777
                    ' ',
2778
                    ' ',
2779
                    ' ',
2780
                    ' ',
2781
                    ' ',
2782
                    ' ',
2783
                    ' ',
2784
                    ' ',
2785
                    ' ',
2786
                    ' '
2787
                );
2788
                $prereq_mod = str_replace($find, $replace, $prereq);
2789
                $ids = explode(' ', $prereq_mod);
2790
                foreach ($ids as $id) {
2791
                    $id = trim($id);
2792
                    if (isset ($this->refs_list[$id])) {
2793
                        $prereq = preg_replace(
2794
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2795
                            'ITEM_'.$this->refs_list[$id],
2796
                            $prereq
2797
                        );
2798
                    }
2799
                }
2800
2801
                return $prereq;
2802
            }
2803
        }
2804
    }
2805
2806
    /**
2807
     * Returns the XML DOM document's node
2808
     * @param	resource	Reference to a list of objects to search for the given ITEM_*
2809
     * @param	string		The identifier to look for
2810
     * @return	mixed		The reference to the element found with that identifier. False if not found
2811
     */
2812
    public function get_scorm_xml_node(& $children, $id)
2813
    {
2814
        for ($i = 0; $i < $children->length; $i++) {
2815
            $item_temp = $children->item($i);
2816
            if ($item_temp->nodeName == 'item') {
2817
                if ($item_temp->getAttribute('identifier') == $id) {
2818
                    return $item_temp;
2819
                }
2820
            }
2821
            $subchildren = $item_temp->childNodes;
2822
            if ($subchildren && $subchildren->length > 0) {
2823
                $val = $this->get_scorm_xml_node($subchildren, $id);
2824
                if (is_object($val)) {
2825
2826
                    return $val;
2827
                }
2828
            }
2829
        }
2830
2831
        return false;
2832
    }
2833
2834
    /**
2835
     * Gets the status list for all LP's items
2836
     * @return	array	Array of [index] => [item ID => current status]
2837
     */
2838
    public function get_items_status_list()
2839
    {
2840
        if ($this->debug > 0) {
2841
            error_log('New LP - In learnpath::get_items_status_list()', 0);
2842
        }
2843
        $list = [];
2844
        foreach ($this->ordered_items as $item_id) {
2845
            $list[] = array(
2846
                $item_id => $this->items[$item_id]->get_status()
2847
            );
2848
        }
2849
        return $list;
2850
    }
2851
2852
    /**
2853
     * Return the number of interactions for the given learnpath Item View ID.
2854
     * This method can be used as static.
2855
     * @param	integer	Item View ID
2856
     * @param   integer course id
2857
     * @return	integer	Number of interactions
2858
     */
2859
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2860
    {
2861
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2862
        $lp_iv_id = intval($lp_iv_id);
2863
        $course_id = intval($course_id);
2864
2865
        $sql = "SELECT count(*) FROM $table
2866
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2867
        $res = Database::query($sql);
2868
        $num = 0;
2869
        if (Database::num_rows($res)) {
2870
            $row = Database::fetch_array($res);
2871
            $num = $row[0];
2872
        }
2873
        return $num;
2874
    }
2875
2876
    /**
2877
     * Return the interactions as an array for the given lp_iv_id.
2878
     * This method can be used as static.
2879
     * @param	integer	Learnpath Item View ID
2880
     * @return	array
2881
     * @todo 	Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2882
     */
2883
    public static function get_iv_interactions_array($lp_iv_id)
2884
    {
2885
        $course_id = api_get_course_int_id();
2886
        $list = [];
2887
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2888
2889
        if (empty($lp_iv_id)) {
2890
            return [];
2891
        }
2892
2893
        $sql = "SELECT * FROM $table
2894
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2895
                ORDER BY order_id ASC";
2896
        $res = Database::query($sql);
2897
        $num = Database::num_rows($res);
2898
        if ($num > 0) {
2899
            $list[] = array(
2900
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2901
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2902
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2903
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2904
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2905
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2906
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2907
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES)
2908
            );
2909
            while ($row = Database::fetch_array($res)) {
2910
                $list[] = array(
2911
                    'order_id' => ($row['order_id'] + 1),
2912
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2913
                    'type' => $row['interaction_type'],
2914
                    'time' => $row['completion_time'],
2915
                    //'correct_responses' => $row['correct_responses'],
2916
                    'correct_responses' => '', // Hide correct responses from students.
2917
                    'student_response' => $row['student_response'],
2918
                    'result' => $row['result'],
2919
                    'latency' => $row['latency']
2920
                );
2921
            }
2922
        }
2923
2924
        return $list;
2925
    }
2926
2927
    /**
2928
     * Return the number of objectives for the given learnpath Item View ID.
2929
     * This method can be used as static.
2930
     * @param	integer	Item View ID
2931
     * @return	integer	Number of objectives
2932
     */
2933
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2934
    {
2935
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2936
        $course_id = intval($course_id);
2937
        $lp_iv_id = intval($lp_iv_id);
2938
        $sql = "SELECT count(*) FROM $table
2939
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2940
        //@todo seems that this always returns 0
2941
        $res = Database::query($sql);
2942
        $num = 0;
2943
        if (Database::num_rows($res)) {
2944
            $row = Database::fetch_array($res);
2945
            $num = $row[0];
2946
        }
2947
2948
        return $num;
2949
    }
2950
2951
    /**
2952
     * Return the objectives as an array for the given lp_iv_id.
2953
     * This method can be used as static.
2954
     * @param	integer	Learnpath Item View ID
2955
     * @return	array
2956
     * @todo 	Translate labels
2957
     */
2958
    public static function get_iv_objectives_array($lp_iv_id = 0)
2959
    {
2960
        $course_id = api_get_course_int_id();
2961
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2962
        $sql = "SELECT * FROM $table
2963
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id
2964
                ORDER BY order_id ASC";
2965
        $res = Database::query($sql);
2966
        $num = Database::num_rows($res);
2967
        $list = [];
2968
        if ($num > 0) {
2969
            $list[] = array(
2970
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2971
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
2972
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
2973
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
2974
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
2975
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES)
2976
            );
2977
            while ($row = Database::fetch_array($res)) {
2978
                $list[] = array(
2979
                    'order_id' => ($row['order_id'] + 1),
2980
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2981
                    'score_raw' => $row['score_raw'],
2982
                    'score_max' => $row['score_max'],
2983
                    'score_min' => $row['score_min'],
2984
                    'status' => $row['status']
2985
                );
2986
            }
2987
        }
2988
2989
        return $list;
2990
    }
2991
2992
    /**
2993
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2994
     * used by get_html_toc() to be ready to display
2995
     * @return	array	TOC as a table with 4 elements per row: title, link, status and level
2996
     */
2997
    public function get_toc()
2998
    {
2999
        if ($this->debug > 0) {
3000
            error_log('learnpath::get_toc()', 0);
3001
        }
3002
        $toc = [];
3003
        foreach ($this->ordered_items as $item_id) {
3004
            if ($this->debug > 2) {
3005
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3006
            }
3007
            // TODO: Change this link generation and use new function instead.
3008
            $toc[] = array(
3009
                'id' => $item_id,
3010
                'title' => $this->items[$item_id]->get_title(),
3011
                'status' => $this->items[$item_id]->get_status(),
3012
                'level' => $this->items[$item_id]->get_level(),
3013
                'type' => $this->items[$item_id]->get_type(),
3014
                'description' => $this->items[$item_id]->get_description(),
3015
                'path' => $this->items[$item_id]->get_path(),
3016
                'parent' => $this->items[$item_id]->get_parent(),
3017
            );
3018
        }
3019
        if ($this->debug > 2) {
3020
            error_log('New LP - In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3021
        }
3022
        return $toc;
3023
    }
3024
3025
    /**
3026
     * Generate and return the table of contents for this learnpath. The JS
3027
     * table returned is used inside of scorm_api.php
3028
     * @param string $varname
3029
     * @return  string  A JS array vairiable construction
3030
     */
3031
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3032
    {
3033
        if ($this->debug > 0) {
3034
            error_log('New LP - In learnpath::get_items_details_as_js()', 0);
3035
        }
3036
        $toc = $varname.' = new Array();';
3037
        foreach ($this->ordered_items as $item_id) {
3038
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3039
        }
3040
        if ($this->debug > 2) {
3041
            error_log('New LP - In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3042
        }
3043
        return $toc;
3044
    }
3045
3046
    /**
3047
     * Gets the learning path type
3048
     * @param	boolean		Return the name? If false, return the ID. Default is false.
3049
     * @return	mixed		Type ID or name, depending on the parameter
3050
     */
3051
    public function get_type($get_name = false)
3052
    {
3053
        $res = false;
3054
        if ($this->debug > 0) {
3055
            error_log('New LP - In learnpath::get_type()', 0);
3056
        }
3057
        if (!empty ($this->type) && (!$get_name)) {
3058
            $res = $this->type;
3059
        }
3060
        if ($this->debug > 2) {
3061
            error_log('New LP - In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3062
        }
3063
        return $res;
3064
    }
3065
3066
    /**
3067
     * Gets the learning path type as static method
3068
     * @param	int $lp_id
3069
     * @return	mixed		Type ID or name, depending on the parameter
3070
     */
3071
    public static function get_type_static($lp_id = 0)
3072
    {
3073
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3074
        $lp_id = intval($lp_id);
3075
        $sql = "SELECT lp_type FROM $tbl_lp
3076
                WHERE iid = $lp_id";
3077
        $res = Database::query($sql);
3078
        if ($res === false) {
3079
            return null;
3080
        }
3081
        if (Database::num_rows($res) <= 0) {
3082
            return null;
3083
        }
3084
        $row = Database::fetch_array($res);
3085
        return $row['lp_type'];
3086
    }
3087
3088
    /**
3089
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3090
     * This method can be used as abstract and is recursive
3091
     * @param	integer	Learnpath ID
3092
     * @param	integer	Parent ID of the items to look for
3093
     * @return	array	Ordered list of item IDs (empty array on error)
3094
     */
3095
    public static function get_flat_ordered_items_list($lp, $parent = 0, $course_id = 0)
3096
    {
3097
        if (empty($course_id)) {
3098
            $course_id = api_get_course_int_id();
3099
        } else {
3100
            $course_id = intval($course_id);
3101
        }
3102
        $list = [];
3103
3104
        if (empty($lp)) {
3105
            return $list;
3106
        }
3107
3108
        $lp = intval($lp);
3109
        $parent = intval($parent);
3110
3111
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3112
        $sql = "SELECT iid FROM $tbl_lp_item
3113
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3114
                ORDER BY display_order";
3115
3116
        $res = Database::query($sql);
3117
        while ($row = Database::fetch_array($res)) {
3118
            $sublist = self::get_flat_ordered_items_list(
3119
                $lp,
3120
                $row['iid'],
3121
                $course_id
3122
            );
3123
            $list[] = $row['iid'];
3124
            foreach ($sublist as $item) {
3125
                $list[] = $item;
3126
            }
3127
        }
3128
        return $list;
3129
    }
3130
3131
    /**
3132
     * @return array
3133
     */
3134
    public static function getChapterTypes()
3135
    {
3136
        return array(
3137
            'dir'
3138
        );
3139
    }
3140
3141
    /**
3142
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display
3143
     * @return	string	HTML TOC ready to display
3144
     */
3145
    public function getParentToc($tree)
3146
    {
3147
        if ($this->debug > 0) {
3148
            error_log('In learnpath::get_html_toc()', 0);
3149
        }
3150
        if (empty($tree)) {
3151
            $tree = $this->get_toc();
3152
        }
3153
        $dirTypes = self::getChapterTypes();
3154
        $myCurrentId = $this->get_current_item_id();
3155
        $listParent = [];
3156
        $listChildren = [];
3157
        $listNotParent = [];
3158
        $list = [];
3159
        foreach ($tree as $subtree) {
3160
            if (in_array($subtree['type'], $dirTypes)){
3161
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3162
                $subtree['children'] = $listChildren;
3163
                if (!empty($subtree['children'])) {
3164
                    foreach ($subtree['children'] as $subItem) {
3165
                        if ($subItem['id'] == $this->current) {
3166
                            $subtree['parent_current'] = 'in';
3167
                            $subtree['current'] = 'on';
3168
                        }
3169
                    }
3170
                }
3171
                $listParent[] =  $subtree;
3172
            }
3173
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null ) {
3174
                $classStatus = [
3175
                    'not attempted' => 'scorm_not_attempted',
3176
                    'incomplete' => 'scorm_not_attempted',
3177
                    'failed' => 'scorm_failed',
3178
                    'completed' => 'scorm_completed',
3179
                    'passed' => 'scorm_completed',
3180
                    'succeeded' => 'scorm_completed',
3181
                    'browsed' => 'scorm_completed',
3182
                ];
3183
3184
                if (isset($classStatus[$subtree['status']])) {
3185
                    $cssStatus = $classStatus[$subtree['status']];
3186
                }
3187
3188
                $title = Security::remove_XSS($subtree['title']);
3189
                unset($subtree['title']);
3190
3191
                if (empty ($title)) {
3192
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3193
                }
3194
                $classStyle = null;
3195
                if ($subtree['id'] == $this->current) {
3196
                    $classStyle = 'scorm_item_normal '. $classStyle . 'scorm_highlight';
3197
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3198
                    $classStyle = 'scorm_item_normal '. $classStyle . ' ';
3199
                }
3200
                $subtree['title'] = $title;
3201
                $subtree['class'] = $cssStatus . ' ' .$classStyle;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cssStatus does not seem to be defined for all execution paths leading up to this point.
Loading history...
3202
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3203
                $subtree['current_id'] = $myCurrentId;
3204
                $listNotParent[] = $subtree;
3205
            }
3206
        }
3207
3208
        $list['are_parents'] = $listParent;
3209
        $list['not_parents'] = $listNotParent;
3210
3211
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list returns the type array<mixed,array> which is incompatible with the documented return type string.
Loading history...
3212
    }
3213
3214
    /**
3215
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display
3216
     * @return	string	HTML TOC ready to display
3217
     */
3218
    public function getChildrenToc($tree, $id, $parent = true)
3219
    {
3220
        if ($this->debug > 0) {
3221
            error_log('In learnpath::get_html_toc()', 0);
3222
        }
3223
        if (empty($tree)) {
3224
            $tree = $this->get_toc();
3225
        }
3226
3227
        $dirTypes = self::getChapterTypes();
3228
        $mycurrentitemid = $this->get_current_item_id();
3229
        $list = [];
3230
        $classStatus = [
3231
            'not attempted' => 'scorm_not_attempted',
3232
            'incomplete' => 'scorm_not_attempted',
3233
            'failed' => 'scorm_failed',
3234
            'completed' => 'scorm_completed',
3235
            'passed' => 'scorm_completed',
3236
            'succeeded' => 'scorm_completed',
3237
            'browsed' => 'scorm_completed',
3238
        ];
3239
3240
        foreach ($tree as $subtree) {
3241
            $subtree['tree'] = null;
3242
3243
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id ) {
3244
                if ($subtree['id'] == $this->current) {
3245
                    $subtree['current'] = 'active';
3246
                } else {
3247
                    $subtree['current'] = null;
3248
                }
3249
                if (isset($classStatus[$subtree['status']])) {
3250
                    $cssStatus = $classStatus[$subtree['status']];
3251
                }
3252
3253
                $title = Security::remove_XSS($subtree['title']);
3254
                unset($subtree['title']);
3255
                if (empty ($title)) {
3256
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3257
                }
3258
3259
                $classStyle = null;
3260
                if ($subtree['id'] == $this->current) {
3261
                    $classStyle = 'scorm_item_normal '. $classStyle . 'scorm_highlight';
3262
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3263
                    $classStyle = 'scorm_item_normal '. $classStyle . ' ';
3264
                }
3265
3266
                if (in_array($subtree['type'], $dirTypes)) {
3267
                    $subtree['title'] = stripslashes($title);
3268
                } else {
3269
                    $subtree['title'] = $title;
3270
                    $subtree['class'] = $cssStatus . ' ' .$classStyle;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cssStatus does not seem to be defined for all execution paths leading up to this point.
Loading history...
3271
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3272
                    $subtree['current_id'] = $mycurrentitemid;
3273
                }
3274
                $list[] =  $subtree;
3275
            }
3276
        }
3277
3278
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list returns the type array which is incompatible with the documented return type string.
Loading history...
3279
    }
3280
3281
    /**
3282
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display
3283
     * @param array $toc_list
3284
     * @return	string	HTML TOC ready to display
3285
     */
3286
    public function getListArrayToc($toc_list = [])
3287
    {
3288
        if ($this->debug > 0) {
3289
            error_log('In learnpath::get_html_toc()', 0);
3290
        }
3291
        if (empty($toc_list)) {
3292
            $toc_list = $this->get_toc();
3293
        }
3294
        // Temporary variables.
3295
        $mycurrentitemid = $this->get_current_item_id();
3296
        $list = [];
3297
        $arrayList = [];
3298
        $classStatus = [
3299
            'not attempted' => 'scorm_not_attempted',
3300
            'incomplete' => 'scorm_not_attempted',
3301
            'failed' => 'scorm_failed',
3302
            'completed' => 'scorm_completed',
3303
            'passed' => 'scorm_completed',
3304
            'succeeded' => 'scorm_completed',
3305
            'browsed' => 'scorm_completed',
3306
        ];
3307
3308
        foreach ($toc_list as $item) {
3309
            $list['id'] = $item['id'];
3310
            $list['status'] = $item['status'];
3311
            $cssStatus = null;
3312
3313
            if (isset($classStatus[$item['status']])) {
3314
                $cssStatus = $classStatus[$item['status']];
3315
            }
3316
3317
            $classStyle = ' ';
3318
            $dirTypes = self::getChapterTypes();
3319
3320
            if (in_array($item['type'], $dirTypes)) {
3321
                $classStyle = 'scorm_item_section ';
3322
            }
3323
            if ($item['id'] == $this->current) {
3324
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3325
            } elseif (!in_array($item['type'], $dirTypes)) {
3326
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3327
            }
3328
            $title = $item['title'];
3329
            if (empty($title)) {
3330
                $title = self::rl_get_resource_name(
3331
                    api_get_course_id(),
3332
                    $this->get_id(),
3333
                    $item['id']
3334
                );
3335
            }
3336
            $title = Security::remove_XSS($item['title']);
3337
3338
            if (empty($item['description'])) {
3339
                $list['description'] = $title;
3340
            } else {
3341
                $list['description'] = $item['description'];
3342
            }
3343
3344
            $list['class'] = $classStyle.' '.$cssStatus;
3345
            $list['level'] = $item['level'];
3346
            $list['type'] = $item['type'];
3347
3348
            if (in_array($item['type'], $dirTypes)) {
3349
                $list['css_level'] = 'level_'.$item['level'];
3350
            } else {
3351
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.learnpath::format_scorm_type_item($item['type']);
3352
            }
3353
3354
            if (in_array($item['type'], $dirTypes)) {
3355
                $list['title'] = stripslashes($title);
3356
            } else {
3357
                $list['title'] = stripslashes($title);
3358
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3359
                $list['current_id'] = $mycurrentitemid;
3360
            }
3361
            $arrayList[] = $list;
3362
        }
3363
3364
        return $arrayList;
3365
    }
3366
3367
    /**
3368
     * Returns an HTML-formatted string ready to display with teacher buttons
3369
     * in LP view menu
3370
     * @return	string	HTML TOC ready to display
3371
     */
3372
    public function get_teacher_toc_buttons()
3373
    {
3374
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true, false, false);
3375
        $hide_teacher_icons_lp = api_get_configuration_value('hide_teacher_icons_lp');
3376
        $html = '';
3377
        if ($is_allowed_to_edit && $hide_teacher_icons_lp == false) {
3378
            if ($this->get_lp_session_id() == api_get_session_id()) {
3379
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3380
                $html .= '<div class="btn-group">';
3381
                $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'>".
3382
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3383
                $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'>".
3384
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3385
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3386
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3387
                $html .= '</div>';
3388
                $html .= '</div>';
3389
            }
3390
        }
3391
3392
        return $html;
3393
    }
3394
3395
    /**
3396
     * Gets the learnpath maker name - generally the editor's name
3397
     * @return	string	Learnpath maker name
3398
     */
3399
    public function get_maker()
3400
    {
3401
        if ($this->debug > 0) {
3402
            error_log('New LP - In learnpath::get_maker()', 0);
3403
        }
3404
        if (!empty ($this->maker)) {
3405
            return $this->maker;
3406
        } else {
3407
            return '';
3408
        }
3409
    }
3410
3411
    /**
3412
     * Gets the learnpath name/title
3413
     * @return	string	Learnpath name/title
3414
     */
3415
    public function get_name()
3416
    {
3417
        if ($this->debug > 0) {
3418
            error_log('New LP - In learnpath::get_name()', 0);
3419
        }
3420
        if (!empty ($this->name)) {
3421
            return $this->name;
3422
        } else {
3423
            return 'N/A';
3424
        }
3425
    }
3426
3427
    /**
3428
     * Gets a link to the resource from the present location, depending on item ID.
3429
     * @param	string	$type Type of link expected
3430
     * @param	integer	$item_id Learnpath item ID
3431
     * @return	string	$provided_toc Link to the lp_item resource
3432
     */
3433
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3434
    {
3435
        $course_id = $this->get_course_int_id();
3436
3437
        if ($this->debug > 0) {
3438
            error_log('New LP - In learnpath::get_link('.$type.','.$item_id.')', 0);
3439
        }
3440
        if (empty($item_id)) {
3441
            if ($this->debug > 2) {
3442
                error_log('New LP - In learnpath::get_link() - no item id given in learnpath::get_link(), using current: '.$this->get_current_item_id(), 0);
3443
            }
3444
            $item_id = $this->get_current_item_id();
3445
        }
3446
3447
        if (empty($item_id)) {
3448
            if ($this->debug > 2) {
3449
                error_log('New LP - In learnpath::get_link() - no current item id found in learnpath object', 0);
3450
            }
3451
            //still empty, this means there was no item_id given and we are not in an object context or
3452
            //the object property is empty, return empty link
3453
            $item_id = $this->first();
3454
            return '';
3455
        }
3456
3457
        $file = '';
3458
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3459
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3460
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3461
        $item_id = intval($item_id);
3462
3463
        $sql = "SELECT
3464
                    l.lp_type as ltype,
3465
                    l.path as lpath,
3466
                    li.item_type as litype,
3467
                    li.path as lipath,
3468
                    li.parameters as liparams
3469
        		FROM $lp_table l
3470
                INNER JOIN $lp_item_table li
3471
                ON (li.lp_id = l.iid)
3472
        		WHERE 
3473
        		    li.iid = $item_id 
3474
        		";
3475
        if ($this->debug > 2) {
3476
            error_log('New LP - In learnpath::get_link() - selecting item '.$sql, 0);
3477
        }
3478
        $res = Database::query($sql);
3479
        if (Database::num_rows($res) > 0) {
3480
            $row = Database::fetch_array($res);
3481
            $lp_type = $row['ltype'];
3482
            $lp_path = $row['lpath'];
3483
            $lp_item_type = $row['litype'];
3484
            $lp_item_path = $row['lipath'];
3485
            $lp_item_params = $row['liparams'];
3486
3487
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3488
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3489
            }
3490
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3491
            if ($type == 'http') {
3492
                //web path
3493
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3494
            } else {
3495
                $course_path = $sys_course_path; //system path
3496
            }
3497
3498
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3499
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3500
            if (in_array($lp_item_type,
3501
                array('quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication'))
3502
            ) {
3503
                $lp_type = 1;
3504
            }
3505
3506
            if ($this->debug > 2) {
3507
                error_log('New LP - In learnpath::get_link() - $lp_type '.$lp_type, 0);
3508
                error_log('New LP - In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3509
            }
3510
3511
            // Now go through the specific cases to get the end of the path
3512
            // @todo Use constants instead of int values.
3513
            switch ($lp_type) {
3514
                case 1 :
3515
                    $file = self::rl_get_resource_link_for_learnpath(
3516
                        $course_id,
3517
                        $this->get_id(),
3518
                        $item_id,
3519
                        $this->get_view_id()
3520
                    );
3521
                    if ($this->debug > 0) {
3522
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3523
                    }
3524
3525
                    switch ($lp_item_type) {
3526
                        case 'dir':
3527
                            $file = 'lp_content.php?type=dir';
3528
                            break;
3529
                        case 'link':
3530
                            if (Link::is_youtube_link($file)) {
3531
                                $src = Link::get_youtube_video_id($file);
3532
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3533
                            } elseif (Link::isVimeoLink($file)) {
3534
                                $src = Link::getVimeoLinkId($file);
3535
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
0 ignored issues
show
Bug introduced by
Are you sure $src of type false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3535
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='./** @scrutinizer ignore-type */ $src;
Loading history...
3536
                            } else {
3537
                                // If the current site is HTTPS and the link is
3538
                                // HTTP, browsers will refuse opening the link
3539
                                $urlId = api_get_current_access_url_id();
3540
                                $url = api_get_access_url($urlId, false);
3541
                                $protocol = substr($url['url'], 0, 5);
3542
                                if ($protocol === 'https') {
3543
                                    $linkProtocol = substr($file, 0, 5);
3544
                                    if ($linkProtocol === 'http:') {
3545
                                        //this is the special intervention case
3546
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3547
                                    }
3548
                                }
3549
                            }
3550
                            break;
3551
                        case 'quiz':
3552
                            // Check how much attempts of a exercise exits in lp
3553
                            $lp_item_id = $this->get_current_item_id();
3554
                            $lp_view_id = $this->get_view_id();
3555
3556
                            $prevent_reinit = null;
3557
                            if (isset($this->items[$this->current])) {
3558
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3559
                            }
3560
3561
                            if (empty($provided_toc)) {
3562
                                if ($this->debug > 0) {
3563
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3564
                                }
3565
                                $list = $this->get_toc();
3566
                            } else {
3567
                                if ($this->debug > 0) {
3568
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3569
                                }
3570
                                $list = $provided_toc;
3571
                            }
3572
3573
                            $type_quiz = false;
3574
3575
                            foreach ($list as $toc) {
3576
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3577
                                    $type_quiz = true;
3578
                                }
3579
                            }
3580
3581
                            if ($type_quiz) {
3582
                                $lp_item_id = intval($lp_item_id);
3583
                                $lp_view_id = intval($lp_view_id);
3584
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3585
                                        WHERE
3586
                                            c_id = $course_id AND
3587
                                            lp_item_id='".$lp_item_id."' AND
3588
                                            lp_view_id ='".$lp_view_id."' AND
3589
                                            status='completed'";
3590
                                $result = Database::query($sql);
3591
                                $row_count = Database:: fetch_row($result);
3592
                                $count_item_view = (int) $row_count[0];
3593
                                $not_multiple_attempt = 0;
3594
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3595
                                    $not_multiple_attempt = 1;
3596
                                }
3597
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3598
                            }
3599
                            break;
3600
                    }
3601
3602
                    $tmp_array = explode('/', $file);
3603
                    $document_name = $tmp_array[count($tmp_array) - 1];
3604
                    if (strpos($document_name, '_DELETED_')) {
3605
                        $file = 'blank.php?error=document_deleted';
3606
                    }
3607
3608
                    break;
3609
                case 2 :
3610
                    if ($this->debug > 2) {
3611
                        error_log('New LP - In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3612
                    }
3613
3614
                    if ($lp_item_type != 'dir') {
3615
                        // Quite complex here:
3616
                        // We want to make sure 'http://' (and similar) links can
3617
                        // be loaded as is (withouth the Chamilo path in front) but
3618
                        // some contents use this form: resource.htm?resource=http://blablabla
3619
                        // which means we have to find a protocol at the path's start, otherwise
3620
                        // it should not be considered as an external URL.
3621
                        // if ($this->prerequisites_match($item_id)) {
3622
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3623
                            if ($this->debug > 2) {
3624
                                error_log('New LP - In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3625
                            }
3626
                            // Distant url, return as is.
3627
                            $file = $lp_item_path;
3628
                        } else {
3629
                            if ($this->debug > 2) {
3630
                                error_log('New LP - In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3631
                            }
3632
                            // Prevent getting untranslatable urls.
3633
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3634
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3635
                            // Prepare the path.
3636
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3637
                            // TODO: Fix this for urls with protocol header.
3638
                            $file = str_replace('//', '/', $file);
3639
                            $file = str_replace(':/', '://', $file);
3640
                            if (substr($lp_path, -1) == '/') {
3641
                                $lp_path = substr($lp_path, 0, -1);
3642
                            }
3643
3644
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3645
                                // if file not found.
3646
                                $decoded = html_entity_decode($lp_item_path);
3647
                                list ($decoded) = explode('?', $decoded);
3648
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3649
                                    $file = self::rl_get_resource_link_for_learnpath(
3650
                                        $course_id,
3651
                                        $this->get_id(),
3652
                                        $item_id,
3653
                                        $this->get_view_id()
3654
                                    );
3655
                                    if (empty($file)) {
3656
                                        $file = 'blank.php?error=document_not_found';
3657
                                    } else {
3658
                                        $tmp_array = explode('/', $file);
3659
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3660
                                        if (strpos($document_name, '_DELETED_')) {
3661
                                            $file = 'blank.php?error=document_deleted';
3662
                                        } else {
3663
                                            $file = 'blank.php?error=document_not_found';
3664
                                        }
3665
                                    }
3666
                                } else {
3667
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3668
                                }
3669
                            }
3670
                        }
3671
3672
                        // We want to use parameters if they were defined in the imsmanifest
3673
                        if (strpos($file, 'blank.php') === false) {
3674
                            $lp_item_params = ltrim($lp_item_params, '?');
3675
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3676
                        }
3677
                    } else {
3678
                        $file = 'lp_content.php?type=dir';
3679
                    }
3680
                    break;
3681
                case 3 :
3682
                    if ($this->debug > 2) {
3683
                        error_log('New LP - In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3684
                    }
3685
                    // Formatting AICC HACP append URL.
3686
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3687
                    if (!empty($lp_item_params)) {
3688
                        $aicc_append .= $lp_item_params.'&';
3689
                    }
3690
                    if ($lp_item_type != 'dir') {
3691
                        // Quite complex here:
3692
                        // We want to make sure 'http://' (and similar) links can
3693
                        // be loaded as is (withouth the Chamilo path in front) but
3694
                        // some contents use this form: resource.htm?resource=http://blablabla
3695
                        // which means we have to find a protocol at the path's start, otherwise
3696
                        // it should not be considered as an external URL.
3697
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3698
                            if ($this->debug > 2) {
3699
                                error_log('New LP - In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3700
                            }
3701
                            // Distant url, return as is.
3702
                            $file = $lp_item_path;
3703
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3704
                            /*
3705
                            if (stristr($file,'<servername>') !== false) {
3706
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3707
                            }
3708
                            */
3709
                            if (stripos($file, '<servername>') !== false) {
3710
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3711
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3712
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3713
                            }
3714
                            //
3715
                            $file .= $aicc_append;
3716
                        } else {
3717
                            if ($this->debug > 2) {
3718
                                error_log('New LP - In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3719
                            }
3720
                            // Prevent getting untranslatable urls.
3721
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3722
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3723
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3724
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3725
                            // TODO: Fix this for urls with protocol header.
3726
                            $file = str_replace('//', '/', $file);
3727
                            $file = str_replace(':/', '://', $file);
3728
                            $file .= $aicc_append;
3729
                        }
3730
                    } else {
3731
                        $file = 'lp_content.php?type=dir';
3732
                    }
3733
                    break;
3734
                case 4 :
3735
                    break;
3736
                default:
3737
                    break;
3738
            }
3739
            // Replace &amp; by & because &amp; will break URL with params
3740
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3741
        }
3742
        if ($this->debug > 2) {
3743
            error_log('New LP - In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3744
        }
3745
        return $file;
3746
    }
3747
3748
    /**
3749
     * Gets the latest usable view or generate a new one
3750
     * @param	integer	Optional attempt number. If none given, takes the highest from the lp_view table
3751
     * @return	integer	DB lp_view id
3752
     */
3753
    public function get_view($attempt_num = 0)
3754
    {
3755
        if ($this->debug > 0) {
3756
            error_log('New LP - In learnpath::get_view()', 0);
3757
        }
3758
        $search = '';
3759
        // Use $attempt_num to enable multi-views management (disabled so far).
3760
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3761
            $search = 'AND view_count = '.$attempt_num;
3762
        }
3763
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3764
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3765
3766
        $course_id = api_get_course_int_id();
3767
        $sessionId = api_get_session_id();
3768
3769
        $sql = "SELECT iid, view_count FROM $lp_view_table
3770
        		WHERE
3771
        		    c_id = $course_id AND
3772
        		    lp_id = " . $this->get_id()." AND
3773
        		    user_id = " . $this->get_user_id()." AND
3774
        		    session_id = $sessionId
3775
        		    $search
3776
                ORDER BY view_count DESC";
3777
        $res = Database::query($sql);
3778
        if (Database::num_rows($res) > 0) {
3779
            $row = Database::fetch_array($res);
3780
            $this->lp_view_id = $row['iid'];
3781
        } elseif (!api_is_invitee()) {
3782
            // There is no database record, create one.
3783
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3784
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3785
            Database::query($sql);
3786
            $id = Database::insert_id();
3787
            $this->lp_view_id = $id;
3788
3789
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3790
            Database::query($sql);
3791
        }
3792
3793
        return $this->lp_view_id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->lp_view_id also could return the type string which is incompatible with the documented return type integer.
Loading history...
3794
    }
3795
3796
    /**
3797
     * Gets the current view id
3798
     * @return	integer	View ID (from lp_view)
3799
     */
3800
    public function get_view_id()
3801
    {
3802
        if ($this->debug > 0) {
3803
            error_log('New LP - In learnpath::get_view_id()', 0);
3804
        }
3805
        if (!empty ($this->lp_view_id)) {
3806
            return $this->lp_view_id;
3807
        } else {
3808
            return 0;
3809
        }
3810
    }
3811
3812
    /**
3813
     * Gets the update queue
3814
     * @return	array	Array containing IDs of items to be updated by JavaScript
3815
     */
3816
    public function get_update_queue()
3817
    {
3818
        if ($this->debug > 0) {
3819
            error_log('New LP - In learnpath::get_update_queue()', 0);
3820
        }
3821
        return $this->update_queue;
3822
    }
3823
3824
    /**
3825
     * Gets the user ID
3826
     * @return	integer	User ID
3827
     */
3828
    public function get_user_id()
3829
    {
3830
        if ($this->debug > 0) {
3831
            error_log('New LP - In learnpath::get_user_id()', 0);
3832
        }
3833
        if (!empty ($this->user_id)) {
3834
            return $this->user_id;
3835
        } else {
3836
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3837
        }
3838
    }
3839
3840
    /**
3841
     * Checks if any of the items has an audio element attached
3842
     * @return  bool    True or false
3843
     */
3844
    public function has_audio()
3845
    {
3846
        if ($this->debug > 1) {
3847
            error_log('New LP - In learnpath::has_audio()', 0);
3848
        }
3849
        $has = false;
3850
        foreach ($this->items as $i => $item) {
3851
            if (!empty ($this->items[$i]->audio)) {
3852
                $has = true;
3853
                break;
3854
            }
3855
        }
3856
        return $has;
3857
    }
3858
3859
    /**
3860
     * Moves an item up and down at its level
3861
     * @param	integer	Item to move up and down
3862
     * @param	string	Direction 'up' or 'down'
3863
     * @return	integer	New display order, or false on error
3864
     */
3865
    public function move_item($id, $direction)
3866
    {
3867
        $course_id = api_get_course_int_id();
3868
        if ($this->debug > 0) {
3869
            error_log('New LP - In learnpath::move_item('.$id.','.$direction.')', 0);
3870
        }
3871
        if (empty($id) || empty($direction)) {
3872
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3873
        }
3874
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3875
        $sql_sel = "SELECT *
3876
                    FROM $tbl_lp_item
3877
                    WHERE  
3878
                        iid = $id
3879
                    ";
3880
        $res_sel = Database::query($sql_sel);
3881
        // Check if elem exists.
3882
        if (Database::num_rows($res_sel) < 1) {
3883
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3884
        }
3885
        // Gather data.
3886
        $row = Database::fetch_array($res_sel);
3887
        $previous = $row['previous_item_id'];
3888
        $next = $row['next_item_id'];
3889
        $display = $row['display_order'];
3890
        $parent = $row['parent_item_id'];
3891
        $lp = $row['lp_id'];
3892
        // Update the item (switch with previous/next one).
3893
        switch ($direction) {
3894
            case 'up':
3895
                if ($this->debug > 2) {
3896
                    error_log('Movement up detected', 0);
3897
                }
3898
                if ($display > 1) {
3899
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3900
                                 WHERE iid = $previous";
3901
3902
                    if ($this->debug > 2) {
3903
                        error_log('Selecting previous: '.$sql_sel2, 0);
3904
                    }
3905
                    $res_sel2 = Database::query($sql_sel2);
3906
                    if (Database::num_rows($res_sel2) < 1) {
3907
                        $previous_previous = 0;
3908
                    }
3909
                    // Gather data.
3910
                    $row2 = Database::fetch_array($res_sel2);
3911
                    $previous_previous = $row2['previous_item_id'];
3912
                    // Update previous_previous item (switch "next" with current).
3913
                    if ($previous_previous != 0) {
3914
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3915
                                        next_item_id = $id
3916
                                    WHERE iid = $previous_previous";
3917
                        if ($this->debug > 2) {
3918
                            error_log($sql_upd2, 0);
3919
                        }
3920
                        Database::query($sql_upd2);
3921
                    }
3922
                    // Update previous item (switch with current).
3923
                    if ($previous != 0) {
3924
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3925
                                    next_item_id = $next,
3926
                                    previous_item_id = $id,
3927
                                    display_order = display_order +1
3928
                                    WHERE iid = $previous";
3929
                        if ($this->debug > 2) {
3930
                            error_log($sql_upd2, 0);
3931
                        }
3932
                        Database::query($sql_upd2);
3933
                    }
3934
3935
                    // Update current item (switch with previous).
3936
                    if ($id != 0) {
3937
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3938
                                        next_item_id = $previous,
3939
                                        previous_item_id = $previous_previous,
3940
                                        display_order = display_order-1
3941
                                    WHERE c_id = ".$course_id." AND id = $id";
3942
                        if ($this->debug > 2) {
3943
                            error_log($sql_upd2, 0);
3944
                        }
3945
                        Database::query($sql_upd2);
3946
                    }
3947
                    // Update next item (new previous item).
3948
                    if ($next != 0) {
3949
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3950
                                     WHERE iid = $next";
3951
                        if ($this->debug > 2) {
3952
                            error_log($sql_upd2, 0);
3953
                        }
3954
                        Database::query($sql_upd2);
3955
                    }
3956
                    $display = $display - 1;
3957
                }
3958
                break;
3959
            case 'down':
3960
                if ($this->debug > 2) {
3961
                    error_log('Movement down detected', 0);
3962
                }
3963
                if ($next != 0) {
3964
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
3965
                                 WHERE iid = $next";
3966
                    if ($this->debug > 2) {
3967
                        error_log('Selecting next: '.$sql_sel2, 0);
3968
                    }
3969
                    $res_sel2 = Database::query($sql_sel2);
3970
                    if (Database::num_rows($res_sel2) < 1) {
3971
                        $next_next = 0;
3972
                    }
3973
                    // Gather data.
3974
                    $row2 = Database::fetch_array($res_sel2);
3975
                    $next_next = $row2['next_item_id'];
3976
                    // Update previous item (switch with current).
3977
                    if ($previous != 0) {
3978
                        $sql_upd2 = "UPDATE $tbl_lp_item 
3979
                                     SET next_item_id = $next
3980
                                     WHERE iid = $previous";
3981
                        Database::query($sql_upd2);
3982
                    }
3983
                    // Update current item (switch with previous).
3984
                    if ($id != 0) {
3985
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3986
                                     previous_item_id = $next, 
3987
                                     next_item_id = $next_next, 
3988
                                     display_order = display_order + 1
3989
                                     WHERE iid = $id";
3990
                        Database::query($sql_upd2);
3991
                    }
3992
3993
                    // Update next item (new previous item).
3994
                    if ($next != 0) {
3995
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3996
                                     previous_item_id = $previous, 
3997
                                     next_item_id = $id, 
3998
                                     display_order = display_order-1
3999
                                     WHERE iid = $next";
4000
                        Database::query($sql_upd2);
4001
                    }
4002
4003
                    // Update next_next item (switch "previous" with current).
4004
                    if ($next_next != 0) {
4005
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4006
                                     previous_item_id = $id
4007
                                     WHERE iid = $next_next";
4008
                        Database::query($sql_upd2);
4009
                    }
4010
                    $display = $display + 1;
4011
                }
4012
                break;
4013
            default:
4014
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
4015
        }
4016
        return $display;
4017
    }
4018
4019
    /**
4020
     * Move a LP up (display_order)
4021
     * @param int $lp_id Learnpath ID
4022
     * @param int $categoryId
4023
     * @return bool
4024
     */
4025
    public static function move_up($lp_id, $categoryId = 0)
4026
    {
4027
        $courseId = api_get_course_int_id();
4028
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4029
4030
        $categoryCondition = '';
4031
        if (!empty($categoryId)) {
4032
            $categoryId = (int) $categoryId;
4033
            $categoryCondition = " AND category_id = $categoryId";
4034
        }
4035
        $sql = "SELECT * FROM $lp_table
4036
                WHERE c_id = $courseId
4037
                $categoryCondition
4038
                ORDER BY display_order";
4039
        $res = Database::query($sql);
4040
        if ($res === false) {
4041
            return false;
4042
        }
4043
4044
        $lps = [];
4045
        $lp_order = [];
4046
        $num = Database::num_rows($res);
4047
        // First check the order is correct, globally (might be wrong because
4048
        // of versions < 1.8.4)
4049
        if ($num > 0) {
4050
            $i = 1;
4051
            while ($row = Database::fetch_array($res)) {
4052
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4053
                    $sql = "UPDATE $lp_table SET display_order = $i
4054
                            WHERE iid = ".$row['iid'];
4055
                    Database::query($sql);
4056
                }
4057
                $row['display_order'] = $i;
4058
                $lps[$row['iid']] = $row;
4059
                $lp_order[$i] = $row['iid'];
4060
                $i++;
4061
            }
4062
        }
4063
        if ($num > 1) { // If there's only one element, no need to sort.
4064
            $order = $lps[$lp_id]['display_order'];
4065
            if ($order > 1) { // If it's the first element, no need to move up.
4066
                $sql = "UPDATE $lp_table SET display_order = $order
4067
                        WHERE iid = ".$lp_order[$order - 1];
4068
                Database::query($sql);
4069
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4070
                        WHERE iid = $lp_id";
4071
                Database::query($sql);
4072
            }
4073
        }
4074
4075
        return true;
4076
    }
4077
4078
    /**
4079
     * Move a learnpath down (display_order)
4080
     * @param int $lp_id Learnpath ID
4081
     * @param int $categoryId
4082
     * @return bool
4083
     */
4084
    public static function move_down($lp_id, $categoryId = 0)
4085
    {
4086
        $courseId = api_get_course_int_id();
4087
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4088
4089
        $categoryCondition = '';
4090
        if (!empty($categoryId)) {
4091
            $categoryId = (int) $categoryId;
4092
            $categoryCondition = " AND category_id = $categoryId";
4093
        }
4094
4095
        $sql = "SELECT * FROM $lp_table
4096
                WHERE c_id = $courseId
4097
                $categoryCondition
4098
                ORDER BY display_order";
4099
        $res = Database::query($sql);
4100
        if ($res === false) {
4101
            return false;
4102
        }
4103
        $lps = [];
4104
        $lp_order = [];
4105
        $num = Database::num_rows($res);
4106
        $max = 0;
4107
        // First check the order is correct, globally (might be wrong because
4108
        // of versions < 1.8.4).
4109
        if ($num > 0) {
4110
            $i = 1;
4111
            while ($row = Database::fetch_array($res)) {
4112
                $max = $i;
4113
                if ($row['display_order'] != $i) {
4114
                    // If we find a gap in the order, we need to fix it.
4115
                    $sql = "UPDATE $lp_table SET display_order = $i
4116
                              WHERE iid = ".$row['iid'];
4117
                    Database::query($sql);
4118
                }
4119
                $row['display_order'] = $i;
4120
                $lps[$row['iid']] = $row;
4121
                $lp_order[$i] = $row['iid'];
4122
                $i++;
4123
            }
4124
        }
4125
        if ($num > 1) { // If there's only one element, no need to sort.
4126
            $order = $lps[$lp_id]['display_order'];
4127
            if ($order < $max) { // If it's the first element, no need to move up.
4128
                $sql = "UPDATE $lp_table SET display_order = $order
4129
                        WHERE iid = ".$lp_order[$order + 1];
4130
                Database::query($sql);
4131
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4132
                        WHERE iid = $lp_id";
4133
                Database::query($sql);
4134
            }
4135
        }
4136
4137
        return true;
4138
    }
4139
4140
    /**
4141
     * Updates learnpath attributes to point to the next element
4142
     * The last part is similar to set_current_item but processing the other way around
4143
     */
4144
    public function next()
4145
    {
4146
        if ($this->debug > 0) {
4147
            error_log('New LP - In learnpath::next()', 0);
4148
        }
4149
        $this->last = $this->get_current_item_id();
4150
        $this->items[$this->last]->save(
4151
            false,
4152
            $this->prerequisites_match($this->last)
4153
        );
4154
        $this->autocomplete_parents($this->last);
4155
        $new_index = $this->get_next_index();
4156
        if ($this->debug > 2) {
4157
            error_log('New LP - New index: '.$new_index, 0);
4158
        }
4159
        $this->index = $new_index;
4160
        if ($this->debug > 2) {
4161
            error_log('New LP - Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4162
        }
4163
        $this->current = $this->ordered_items[$new_index];
4164
        if ($this->debug > 2) {
4165
            error_log('New LP - new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4166
        }
4167
    }
4168
4169
    /**
4170
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4171
     * class, this might be redefined to allow several behaviours depending on the document type.
4172
     * @param integer Resource ID
4173
     */
4174
    public function open($id)
4175
    {
4176
        if ($this->debug > 0) {
4177
            error_log('New LP - In learnpath::open()', 0);
4178
        }
4179
        // TODO:
4180
        // set the current resource attribute to this resource
4181
        // switch on element type (redefine in child class?)
4182
        // set status for this item to "opened"
4183
        // start timer
4184
        // initialise score
4185
        $this->index = 0; //or = the last item seen (see $this->last)
4186
    }
4187
4188
    /**
4189
     * Check that all prerequisites are fulfilled. Returns true and an
4190
     * empty string on success, returns false
4191
     * and the prerequisite string on error.
4192
     * This function is based on the rules for aicc_script language as
4193
     * described in the SCORM 1.2 CAM documentation page 108.
4194
     * @param	integer	$itemId Optional item ID. If none given, uses the current open item.
4195
     * @return	boolean	True if prerequisites are matched, false otherwise -
4196
     * Empty string if true returned, prerequisites string otherwise.
4197
     */
4198
    public function prerequisites_match($itemId = null)
4199
    {
4200
        $debug = $this->debug;
4201
        if ($debug > 0) {
4202
            error_log('In learnpath::prerequisites_match()', 0);
4203
        }
4204
4205
        if (empty($itemId)) {
4206
            $itemId = $this->current;
4207
        }
4208
4209
        $currentItem = $this->getItem($itemId);
4210
4211
        if ($currentItem) {
4212
            if ($this->type == 2) {
4213
                // Getting prereq from scorm
4214
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4215
            } else {
4216
                $prereq_string = $currentItem->get_prereq_string();
4217
            }
4218
4219
            if (empty($prereq_string)) {
4220
                if ($debug > 0) {
4221
                    error_log('Found prereq_string is empty return true');
4222
                }
4223
                return true;
4224
            }
4225
            // Clean spaces.
4226
            $prereq_string = str_replace(' ', '', $prereq_string);
4227
            if ($debug > 0) {
4228
                error_log('Found prereq_string: '.$prereq_string, 0);
4229
            }
4230
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4231
            $result = $currentItem->parse_prereq(
4232
                $prereq_string,
4233
                $this->items,
4234
                $this->refs_list,
4235
                $this->get_user_id()
4236
            );
4237
4238
            if ($result === false) {
4239
                $this->set_error_msg($currentItem->prereq_alert);
4240
            }
4241
        } else {
4242
            $result = true;
4243
            if ($debug > 1) {
4244
                error_log('$this->items['.$itemId.'] was not an object', 0);
4245
            }
4246
        }
4247
4248
        if ($debug > 1) {
4249
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4250
        }
4251
        return $result;
4252
    }
4253
4254
    /**
4255
     * Updates learnpath attributes to point to the previous element
4256
     * The last part is similar to set_current_item but processing the other way around
4257
     */
4258
    public function previous()
4259
    {
4260
        if ($this->debug > 0) {
4261
            error_log('New LP - In learnpath::previous()', 0);
4262
        }
4263
        $this->last = $this->get_current_item_id();
4264
        $this->items[$this->last]->save(
4265
            false,
4266
            $this->prerequisites_match($this->last)
4267
        );
4268
        $this->autocomplete_parents($this->last);
4269
        $new_index = $this->get_previous_index();
4270
        $this->index = $new_index;
4271
        $this->current = $this->ordered_items[$new_index];
4272
    }
4273
4274
    /**
4275
     * Publishes a learnpath. This basically means show or hide the learnpath
4276
     * to normal users.
4277
     * Can be used as abstract
4278
     * @param integer    Learnpath ID
4279
     * @param int   New visibility
4280
     * @return bool
4281
     */
4282
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4283
    {
4284
        $action = 'visible';
4285
        if ($set_visibility != 1) {
4286
            $action = 'invisible';
4287
            self::toggle_publish($lp_id, 'i');
4288
        }
4289
4290
        return api_item_property_update(
4291
            api_get_course_info(),
4292
            TOOL_LEARNPATH,
4293
            $lp_id,
4294
            $action,
4295
            api_get_user_id()
4296
        );
4297
    }
4298
4299
    /**
4300
     * Publishes a learnpath category.
4301
     * This basically means show or hide the learnpath category to normal users.
4302
     * @param int $id
4303
     * @param int $visibility
4304
     * @return bool
4305
     */
4306
    public static function toggleCategoryVisibility($id, $visibility = 1)
4307
    {
4308
        $action = 'visible';
4309
4310
        if ($visibility != 1) {
4311
            $action = 'invisible';
4312
            $list = new LearnpathList(
4313
                api_get_user_id(),
4314
                null,
4315
                null,
4316
                null,
4317
                false,
4318
                $id
4319
            );
4320
4321
            $lpList = $list->get_flat_list();
4322
            foreach ($lpList as $lp) {
4323
                learnpath::toggle_visibility($lp['iid'], 0);
4324
            }
4325
4326
            learnpath::toggleCategoryPublish($id, 0);
4327
        }
4328
4329
        return api_item_property_update(
4330
            api_get_course_info(),
4331
            TOOL_LEARNPATH_CATEGORY,
4332
            $id,
4333
            $action,
4334
            api_get_user_id()
4335
        );
4336
    }
4337
4338
    /**
4339
     * Publishes a learnpath. This basically means show or hide the learnpath
4340
     * on the course homepage
4341
     * Can be used as abstract
4342
     * @param	integer	$lp_id Learnpath id
4343
     * @param	string	$set_visibility New visibility (v/i - visible/invisible)
4344
     * @return bool
4345
     */
4346
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4347
    {
4348
        $course_id = api_get_course_int_id();
4349
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4350
        $lp_id = (int) $lp_id;
4351
        $sql = "SELECT * FROM $tbl_lp
4352
                WHERE iid = $lp_id";
4353
        $result = Database::query($sql);
4354
        if (Database::num_rows($result)) {
4355
            $row = Database::fetch_array($result);
4356
            $name = Database::escape_string($row['name']);
4357
            if ($set_visibility == 'i') {
4358
                $v = 0;
4359
            }
4360
            if ($set_visibility == 'v') {
4361
                $v = 1;
4362
            }
4363
4364
            $session_id = api_get_session_id();
4365
            $session_condition = api_get_session_condition($session_id);
4366
4367
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4368
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4369
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4370
4371
            $sql = "SELECT * FROM $tbl_tool
4372
                    WHERE
4373
                        c_id = $course_id AND
4374
                        (link = '$link' OR link = '$oldLink') AND
4375
                        image = 'scormbuilder.gif' AND
4376
                        (
4377
                            link LIKE '$link%' OR
4378
                            link LIKE '$oldLink%'
4379
                        )
4380
                        $session_condition
4381
                    ";
4382
4383
            $result = Database::query($sql);
4384
            $num = Database::num_rows($result);
4385
            if ($set_visibility == 'i' && $num > 0) {
4386
                $sql = "DELETE FROM $tbl_tool
4387
                        WHERE 
4388
                            c_id = $course_id AND 
4389
                            (link = '$link' OR link = '$oldLink') AND 
4390
                            image='scormbuilder.gif' 
4391
                            $session_condition";
4392
                Database::query($sql);
4393
            } elseif ($set_visibility == 'v' && $num == 0) {
4394
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4395
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $v does not seem to be defined for all execution paths leading up to this point.
Loading history...
4396
                Database::query($sql);
4397
                $insertId = Database::insert_id();
4398
                if ($insertId) {
4399
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4400
                    Database::query($sql);
4401
                }
4402
            } elseif ($set_visibility == 'v' && $num > 0) {
4403
                $sql = "UPDATE $tbl_tool SET
4404
                            c_id = $course_id,
4405
                            name = '$name',
4406
                            link = '$link',
4407
                            image = 'scormbuilder.gif',
4408
                            visibility = '$v',
4409
                            admin = '0',
4410
                            address = 'pastillegris.gif',
4411
                            added_tool = 0,
4412
                            session_id = $session_id
4413
                        WHERE
4414
                            c_id = ".$course_id." AND
4415
                            (link = '$link' OR link = '$oldLink') AND 
4416
                            image='scormbuilder.gif' 
4417
                            $session_condition
4418
                        ";
4419
                Database::query($sql);
4420
            } else {
4421
                // Parameter and database incompatible, do nothing, exit.
4422
                return false;
4423
            }
4424
        } else {
4425
            return false;
4426
        }
4427
    }
4428
4429
    /**
4430
     * Generate the link for a learnpath category as course tool
4431
     * @param int $categoryId
4432
     * @return string
4433
     */
4434
    private static function getCategoryLinkForTool($categoryId)
4435
    {
4436
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
4437
            .http_build_query(
4438
                [
4439
                    'action' => 'view_category',
4440
                    'id' => $categoryId
4441
                ]
4442
            );
4443
4444
        return $link;
4445
    }
4446
4447
    /**
4448
     * Publishes a learnpath.
4449
     * Show or hide the learnpath category on the course homepage
4450
     * @param int $id
4451
     * @param int $setVisibility
4452
     * @return bool
4453
     */
4454
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4455
    {
4456
        $courseId = api_get_course_int_id();
4457
        $sessionId = api_get_session_id();
4458
        $sessionCondition = api_get_session_condition(
4459
            $sessionId,
4460
            true,
4461
            false,
4462
            't.sessionId'
4463
        );
4464
4465
        $em = Database::getManager();
4466
4467
        /** @var CLpCategory $category */
4468
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4469
4470
        if (!$category) {
4471
            return false;
4472
        }
4473
4474
        $link = self::getCategoryLinkForTool($id);
4475
4476
        /** @var CTool $tool */
4477
        $tool = $em->createQuery("
4478
                SELECT t FROM ChamiloCourseBundle:CTool t
4479
                WHERE
4480
                    t.cId = :course AND
4481
                    t.link = :link1 AND
4482
                    t.image = 'lp_category.gif' AND
4483
                    t.link LIKE :link2
4484
                    $sessionCondition
4485
            ")
4486
            ->setParameters([
4487
                'course' => (int) $courseId,
4488
                'link1' => $link,
4489
                'link2' => "$link%"
4490
            ])
4491
            ->getOneOrNullResult();
4492
4493
        if ($setVisibility == 0 && $tool) {
4494
            $em->remove($tool);
4495
            $em->flush();
4496
4497
            return true;
4498
        }
4499
4500
        if ($setVisibility == 1 && !$tool) {
4501
            $tool = new CTool();
4502
            $tool
4503
                ->setCategory('authoring')
4504
                ->setCId($courseId)
4505
                ->setName(strip_tags($category->getName()))
4506
                ->setLink($link)
4507
                ->setImage('lp_category.gif')
4508
                ->setVisibility(1)
4509
                ->setAdmin(0)
4510
                ->setAddress('pastillegris.gif')
4511
                ->setAddedTool(0)
4512
                ->setSessionId($sessionId)
4513
                ->setTarget('_self');
4514
4515
            $em->persist($tool);
4516
            $em->flush();
4517
4518
            $tool->setId($tool->getIid());
4519
4520
            $em->persist($tool);
4521
            $em->flush();
4522
4523
            return true;
4524
        }
4525
4526
        if ($setVisibility == 1 && $tool) {
4527
            $tool
4528
                ->setName(strip_tags($category->getName()))
4529
                ->setVisibility(1);
4530
4531
            $em->persist($tool);
4532
            $em->flush();
4533
4534
            return true;
4535
        }
4536
4537
        return false;
4538
    }
4539
4540
    /**
4541
     * Check if the learnpath category is visible for a user
4542
     * @param CLpCategory $category
4543
     * @param User $user
4544
     * @return bool
4545
     */
4546
    public static function categoryIsVisibleForStudent(
4547
        CLpCategory $category,
4548
        User $user
4549
    ) {
4550
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4551
4552
        if ($isAllowedToEdit) {
4553
            return true;
4554
        }
4555
4556
        $users = $category->getUsers();
4557
4558
        if (empty($users) || !$users->count()) {
4559
            return true;
4560
        }
4561
4562
        if ($category->hasUserAdded($user)) {
4563
            return true;
4564
        }
4565
4566
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4567
        if (!empty($groups)) {
4568
            $em = Database::getManager();
4569
4570
            /** @var ItemPropertyRepository $itemRepo */
4571
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4572
4573
            /** @var CourseRepository $courseRepo */
4574
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4575
            $sessionId = api_get_session_id();
4576
            $session = null;
4577
            if (!empty($sessionId)) {
4578
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4579
            }
4580
4581
            $course = $courseRepo->find(api_get_course_int_id());
4582
4583
            // Subscribed groups to a LP
4584
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4585
                TOOL_LEARNPATH_CATEGORY,
4586
                $category->getId(),
4587
                $course,
4588
                $session
4589
            );
4590
4591
            if (!empty($subscribedGroupsInLp)) {
4592
                $groups = array_column($groups, 'iid');
4593
                /** @var CItemProperty $item */
4594
                foreach ($subscribedGroupsInLp as $item) {
4595
                    if ($item->getGroup() &&
4596
                        in_array($item->getGroup()->getId(), $groups)
4597
                    ) {
4598
                        return true;
4599
                    }
4600
                }
4601
            }
4602
        }
4603
4604
        return false;
4605
    }
4606
4607
    /**
4608
     * Check if a learnpath category is published as course tool
4609
     * @param CLpCategory $category
4610
     * @param int $courseId
4611
     * @return bool
4612
     */
4613
    public static function categoryIsPublished(
4614
        CLpCategory $category,
4615
        $courseId
4616
    ) {
4617
        $link = self::getCategoryLinkForTool($category->getId());
4618
        $em = Database::getManager();
4619
4620
        $tools = $em
4621
            ->createQuery("
4622
                SELECT t FROM ChamiloCourseBundle:CTool t
4623
                WHERE t.cId = :course AND 
4624
                    t.name = :name AND
4625
                    t.image = 'lp_category.gif' AND
4626
                    t.link LIKE :link
4627
            ")
4628
            ->setParameters([
4629
                'course' => $courseId,
4630
                'name' => strip_tags($category->getName()),
4631
                'link' => "$link%"
4632
            ])
4633
            ->getResult();
4634
4635
        /** @var CTool $tool */
4636
        $tool = current($tools);
4637
4638
        return $tool ? $tool->getVisibility() : false;
4639
    }
4640
4641
    /**
4642
     * Restart the whole learnpath. Return the URL of the first element.
4643
     * Make sure the results are saved with anoter method. This method should probably be
4644
     * redefined in children classes.
4645
     * To use a similar method  statically, use the create_new_attempt() method
4646
     * @return bool
4647
     */
4648
    public function restart()
4649
    {
4650
        if ($this->debug > 0) {
4651
            error_log('New LP - In learnpath::restart()', 0);
4652
        }
4653
        // TODO
4654
        // Call autosave method to save the current progress.
4655
        //$this->index = 0;
4656
        if (api_is_invitee()) {
4657
            return false;
4658
        }
4659
        $session_id = api_get_session_id();
4660
        $course_id = api_get_course_int_id();
4661
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4662
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4663
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4664
        if ($this->debug > 2) {
4665
            error_log('New LP - Inserting new lp_view for restart: '.$sql, 0);
4666
        }
4667
        Database::query($sql);
4668
        $view_id = Database::insert_id();
4669
4670
        if ($view_id) {
4671
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4672
            Database::query($sql);
4673
            $this->lp_view_id = $view_id;
4674
            $this->attempt = $this->attempt + 1;
4675
        } else {
4676
            $this->error = 'Could not insert into item_view table...';
4677
            return false;
4678
        }
4679
        $this->autocomplete_parents($this->current);
4680
        foreach ($this->items as $index => $dummy) {
4681
            $this->items[$index]->restart();
4682
            $this->items[$index]->set_lp_view($this->lp_view_id);
4683
        }
4684
        $this->first();
4685
4686
        return true;
4687
    }
4688
4689
    /**
4690
     * Saves the current item
4691
     * @return	boolean
4692
     */
4693
    public function save_current()
4694
    {
4695
        $debug = $this->debug;
4696
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4697
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4698
        if ($debug) {
4699
            error_log('New LP - save_current() saving item '.$this->current, 0);
4700
            error_log(''.print_r($this->items, true), 0);
4701
        }
4702
        if (isset($this->items[$this->current]) &&
4703
            is_object($this->items[$this->current])
4704
        ) {
4705
            if ($debug) {
4706
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->last_scorm_session_time);
4707
            }
4708
4709
            $res = $this->items[$this->current]->save(
4710
                false,
4711
                $this->prerequisites_match($this->current)
4712
            );
4713
            $this->autocomplete_parents($this->current);
4714
            $status = $this->items[$this->current]->get_status();
4715
            $this->update_queue[$this->current] = $status;
4716
4717
            if ($debug) {
4718
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->last_scorm_session_time);
4719
            }
4720
4721
            return $res;
4722
        }
4723
        return false;
4724
    }
4725
4726
    /**
4727
     * Saves the given item
4728
     * @param integer	$item_id Optional (will take from $_REQUEST if null)
4729
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4730
     * @return	bool
4731
     */
4732
    public function save_item($item_id = null, $from_outside = true)
4733
    {
4734
        $debug = $this->debug;
4735
        if ($debug) {
4736
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4737
        }
4738
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4739
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4740
        if (empty($item_id)) {
4741
            $item_id = intval($_REQUEST['id']);
4742
        }
4743
        if (empty($item_id)) {
4744
            $item_id = $this->get_current_item_id();
4745
        }
4746
        if (isset($this->items[$item_id]) &&
4747
            is_object($this->items[$item_id])
4748
        ) {
4749
            if ($debug) {
4750
                error_log('Object exists');
4751
            }
4752
4753
            // Saving the item.
4754
            $res = $this->items[$item_id]->save(
4755
                $from_outside,
4756
                $this->prerequisites_match($item_id)
4757
            );
4758
4759
            if ($debug) {
4760
                error_log('update_queue before:');
4761
                error_log(print_r($this->update_queue, 1));
4762
            }
4763
            $this->autocomplete_parents($item_id);
4764
4765
            $status = $this->items[$item_id]->get_status();
4766
            $this->update_queue[$item_id] = $status;
4767
4768
            if ($debug) {
4769
                error_log('get_status(): '.$status);
4770
                error_log('update_queue after:');
4771
                error_log(print_r($this->update_queue, 1));
4772
            }
4773
            return $res;
4774
        }
4775
        return false;
4776
    }
4777
4778
    /**
4779
     * Saves the last item seen's ID only in case
4780
     */
4781
    public function save_last()
4782
    {
4783
        $course_id = api_get_course_int_id();
4784
        if ($this->debug > 0) {
4785
            error_log('New LP - In learnpath::save_last()', 0);
4786
        }
4787
        $session_condition = api_get_session_condition(
4788
            api_get_session_id(),
4789
            true,
4790
            false
4791
        );
4792
        $table = Database::get_course_table(TABLE_LP_VIEW);
4793
4794
        if (isset($this->current) && !api_is_invitee()) {
4795
            if ($this->debug > 2) {
4796
                error_log('New LP - Saving current item ('.$this->current.') for later review', 0);
4797
            }
4798
            $sql = "UPDATE $table SET
4799
                        last_item = ".intval($this->get_current_item_id())."
4800
                    WHERE
4801
                        c_id = $course_id AND
4802
                        lp_id = ".$this->get_id()." AND
4803
                        user_id = ".$this->get_user_id()." ".$session_condition;
4804
4805
            if ($this->debug > 2) {
4806
                error_log('New LP - Saving last item seen : '.$sql, 0);
4807
            }
4808
            Database::query($sql);
4809
        }
4810
4811
        if (!api_is_invitee()) {
4812
            // Save progress.
4813
            list($progress,) = $this->get_progress_bar_text('%');
4814
            if ($progress >= 0 && $progress <= 100) {
4815
                $progress = (int) $progress;
4816
                $sql = "UPDATE $table SET
4817
                            progress = $progress
4818
                        WHERE
4819
                            c_id = $course_id AND
4820
                            lp_id = " . $this->get_id()." AND
4821
                            user_id = " . $this->get_user_id()." ".$session_condition;
4822
                // Ignore errors as some tables might not have the progress field just yet.
4823
                Database::query($sql);
4824
                $this->progress_db = $progress;
4825
            }
4826
        }
4827
    }
4828
4829
    /**
4830
     * Sets the current item ID (checks if valid and authorized first)
4831
     * @param	integer	$item_id New item ID. If not given or not authorized, defaults to current
4832
     */
4833
    public function set_current_item($item_id = null)
4834
    {
4835
        if ($this->debug > 0) {
4836
            error_log('New LP - In learnpath::set_current_item('.$item_id.')', 0);
4837
        }
4838
        if (empty ($item_id)) {
4839
            if ($this->debug > 2) {
4840
                error_log('New LP - No new current item given, ignore...', 0);
4841
            }
4842
            // Do nothing.
4843
        } else {
4844
            if ($this->debug > 2) {
4845
                error_log('New LP - New current item given is '.$item_id.'...', 0);
4846
            }
4847
            if (is_numeric($item_id)) {
4848
                $item_id = intval($item_id);
4849
                // TODO: Check in database here.
4850
                $this->last = $this->current;
4851
                $this->current = $item_id;
4852
                // TODO: Update $this->index as well.
4853
                foreach ($this->ordered_items as $index => $item) {
4854
                    if ($item == $this->current) {
4855
                        $this->index = $index;
4856
                        break;
4857
                    }
4858
                }
4859
                if ($this->debug > 2) {
4860
                    error_log('New LP - set_current_item('.$item_id.') done. Index is now : '.$this->index, 0);
4861
                }
4862
            } else {
4863
                error_log('New LP - set_current_item('.$item_id.') failed. Not a numeric value: ', 0);
4864
            }
4865
        }
4866
    }
4867
4868
    /**
4869
     * Sets the encoding
4870
     * @param	string	New encoding
4871
     * @return bool
4872
     * TODO (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4873
     */
4874
    public function set_encoding($enc = 'UTF-8')
4875
    {
4876
        if ($this->debug > 0) {
4877
            error_log('New LP - In learnpath::set_encoding()', 0);
4878
        }
4879
4880
        $enc = api_refine_encoding_id($enc);
4881
        if (empty($enc)) {
4882
            $enc = api_get_system_encoding();
4883
        }
4884
        if (api_is_encoding_supported($enc)) {
4885
            $lp = $this->get_id();
4886
            if ($lp != 0) {
4887
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4888
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
4889
                        WHERE iid = ".$lp;
4890
                $res = Database::query($sql);
4891
                return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res returns the type Doctrine\DBAL\Driver\Statement which is incompatible with the documented return type boolean.
Loading history...
4892
            }
4893
        }
4894
        return false;
4895
    }
4896
4897
    /**
4898
     * Sets the JS lib setting in the database directly.
4899
     * This is the JavaScript library file this lp needs to load on startup
4900
     * @param	string	Proximity setting
4901
     * @return  boolean True on update success. False otherwise.
4902
     */
4903
    public function set_jslib($lib = '')
4904
    {
4905
        if ($this->debug > 0) {
4906
            error_log('New LP - In learnpath::set_jslib()', 0);
4907
        }
4908
        $lp = $this->get_id();
4909
        $course_id = api_get_course_int_id();
4910
4911
        if ($lp != 0) {
4912
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4913
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
4914
                    WHERE iid = $lp";
4915
            $res = Database::query($sql);
4916
            return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res returns the type Doctrine\DBAL\Driver\Statement which is incompatible with the documented return type boolean.
Loading history...
4917
        } else {
4918
            return false;
4919
        }
4920
    }
4921
4922
    /**
4923
     * Sets the name of the LP maker (publisher) (and save)
4924
     * @param	string	Optional string giving the new content_maker of this learnpath
4925
     * @return  boolean True
4926
     */
4927
    public function set_maker($name = '')
4928
    {
4929
        if ($this->debug > 0) {
4930
            error_log('New LP - In learnpath::set_maker()', 0);
4931
        }
4932
        if (empty ($name))
4933
            return false;
4934
        $this->maker = $name;
4935
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4936
        $lp_id = $this->get_id();
4937
        $sql = "UPDATE $lp_table SET
4938
                content_maker = '".Database::escape_string($this->maker)."'
4939
                WHERE iid = $lp_id";
4940
        if ($this->debug > 2) {
4941
            error_log('New LP - lp updated with new content_maker : '.$this->maker, 0);
4942
        }
4943
        Database::query($sql);
4944
        return true;
4945
    }
4946
4947
    /**
4948
     * Sets the name of the current learnpath (and save)
4949
     * @param	string	$name Optional string giving the new name of this learnpath
4950
     * @return  boolean True/False
4951
     */
4952
    public function set_name($name = null)
4953
    {
4954
        if ($this->debug > 0) {
4955
            error_log('New LP - In learnpath::set_name()', 0);
4956
        }
4957
        if (empty($name)) {
4958
            return false;
4959
        }
4960
        $this->name = Database::escape_string($name);
4961
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4962
        $lp_id = $this->get_id();
4963
        $course_id = $this->course_info['real_id'];
4964
        $sql = "UPDATE $lp_table SET
4965
                name = '".Database::escape_string($this->name)."'
4966
                WHERE iid = $lp_id";
4967
        if ($this->debug > 2) {
4968
            error_log('New LP - lp updated with new name : '.$this->name, 0);
4969
        }
4970
        $result = Database::query($sql);
4971
        // If the lp is visible on the homepage, change his name there.
4972
        if (Database::affected_rows($result)) {
4973
            $session_id = api_get_session_id();
4974
            $session_condition = api_get_session_condition($session_id);
4975
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4976
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4977
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
4978
            	    WHERE
4979
            	        c_id = $course_id AND
4980
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4981
            Database::query($sql);
4982
            return true;
4983
        } else {
4984
            return false;
4985
        }
4986
    }
4987
4988
    /**
4989
     * Set index specified prefix terms for all items in this path
4990
     * @param   string  Comma-separated list of terms
4991
     * @param   string Xapian term prefix
4992
     * @return  boolean False on error, true otherwise
4993
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment Comma-separated at position 0 could not be parsed: Unknown type name 'Comma-separated' at position 0 in Comma-separated.
Loading history...
4994
    public function set_terms_by_prefix($terms_string, $prefix)
4995
    {
4996
        $course_id = api_get_course_int_id();
4997
        if (api_get_setting('search_enabled') !== 'true')
4998
            return false;
4999
5000
        if (!extension_loaded('xapian')) {
5001
            return false;
5002
        }
5003
5004
        $terms_string = trim($terms_string);
5005
        $terms = explode(',', $terms_string);
5006
        array_walk($terms, 'trim_value');
5007
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5008
5009
        // Don't do anything if no change, verify only at DB, not the search engine.
5010
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0))
5011
            return false;
5012
5013
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5014
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5015
5016
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5017
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5018
        $lp_id = intval($_POST['lp_id']);
5019
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5020
        $result = Database::query($sql);
5021
        $di = new ChamiloIndexer();
5022
5023
        while ($lp_item = Database::fetch_array($result)) {
5024
            // Get search_did.
5025
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5026
            $sql = 'SELECT * FROM %s
5027
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5028
                    LIMIT 1';
5029
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5030
5031
            //echo $sql; echo '<br>';
5032
            $res = Database::query($sql);
5033
            if (Database::num_rows($res) > 0) {
5034
                $se_ref = Database::fetch_array($res);
5035
5036
                // Compare terms.
5037
                $doc = $di->get_document($se_ref['search_did']);
5038
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5039
                $xterms = [];
5040
                foreach ($xapian_terms as $xapian_term) {
5041
                    $xterms[] = substr($xapian_term['name'], 1);
5042
                }
5043
5044
                $dterms = $terms;
5045
                $missing_terms = array_diff($dterms, $xterms);
5046
                $deprecated_terms = array_diff($xterms, $dterms);
5047
5048
                // Save it to search engine.
5049
                foreach ($missing_terms as $term) {
5050
                    $doc->add_term($prefix.$term, 1);
5051
                }
5052
                foreach ($deprecated_terms as $term) {
5053
                    $doc->remove_term($prefix.$term);
5054
                }
5055
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5056
                $di->getDb()->flush();
5057
            }
5058
        }
5059
        return true;
5060
    }
5061
5062
    /**
5063
     * Sets the theme of the LP (local/remote) (and save)
5064
     * @param	string	Optional string giving the new theme of this learnpath
5065
     * @return   bool    Returns true if theme name is not empty
5066
     */
5067
    public function set_theme($name = '')
5068
    {
5069
        if ($this->debug > 0) {
5070
            error_log('New LP - In learnpath::set_theme()', 0);
5071
        }
5072
        $this->theme = $name;
5073
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5074
        $lp_id = $this->get_id();
5075
        $sql = "UPDATE $lp_table SET theme = '".Database::escape_string($this->theme)."'
5076
                WHERE iid = $lp_id";
5077
        if ($this->debug > 2) {
5078
            error_log('New LP - lp updated with new theme : '.$this->theme, 0);
5079
        }
5080
        Database::query($sql);
5081
5082
        return true;
5083
    }
5084
5085
    /**
5086
     * Sets the image of an LP (and save)
5087
     * @param	 string	Optional string giving the new image of this learnpath
5088
     * @return bool   Returns true if theme name is not empty
5089
     */
5090
    public function set_preview_image($name = '')
5091
    {
5092
        if ($this->debug > 0) {
5093
            error_log('New LP - In learnpath::set_preview_image()', 0);
5094
        }
5095
5096
        $this->preview_image = $name;
5097
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5098
        $lp_id = $this->get_id();
5099
        $sql = "UPDATE $lp_table SET
5100
                preview_image = '".Database::escape_string($this->preview_image)."'
5101
                WHERE iid = $lp_id";
5102
        if ($this->debug > 2) {
5103
            error_log('New LP - lp updated with new preview image : '.$this->preview_image, 0);
5104
        }
5105
        Database::query($sql);
5106
        return true;
5107
    }
5108
5109
    /**
5110
     * Sets the author of a LP (and save)
5111
     * @param	string	Optional string giving the new author of this learnpath
5112
     * @return   bool    Returns true if author's name is not empty
5113
     */
5114
    public function set_author($name = '')
5115
    {
5116
        if ($this->debug > 0) {
5117
            error_log('New LP - In learnpath::set_author()', 0);
5118
        }
5119
        $this->author = $name;
0 ignored issues
show
Bug Best Practice introduced by
The property author does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
5120
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5121
        $lp_id = $this->get_id();
5122
        $sql = "UPDATE $lp_table SET author = '".Database::escape_string($name)."'
5123
                WHERE iid = $lp_id";
5124
        if ($this->debug > 2) {
5125
            error_log('New LP - lp updated with new preview author : '.$this->author, 0);
5126
        }
5127
        Database::query($sql);
5128
5129
        return true;
5130
    }
5131
5132
    /**
5133
     * Sets the hide_toc_frame parameter of a LP (and save)
5134
     * @param	int	1 if frame is hidden 0 then else
5135
     * @return   bool    Returns true if author's name is not empty
5136
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 1 at position 0 could not be parsed: Unknown type name '1' at position 0 in 1.
Loading history...
5137
    public function set_hide_toc_frame($hide)
5138
    {
5139
        if ($this->debug > 0) {
5140
            error_log('New LP - In learnpath::set_hide_toc_frame()', 0);
5141
        }
5142
        if (intval($hide) == $hide) {
5143
            $this->hide_toc_frame = $hide;
0 ignored issues
show
Bug Best Practice introduced by
The property hide_toc_frame does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
5144
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5145
            $lp_id = $this->get_id();
5146
            $sql = "UPDATE $lp_table SET
5147
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5148
                    WHERE iid = $lp_id";
5149
            if ($this->debug > 2) {
5150
                error_log('New LP - lp updated with new preview hide_toc_frame : '.$this->author, 0);
5151
            }
5152
            Database::query($sql);
5153
5154
            return true;
5155
        } else {
5156
            return false;
5157
        }
5158
    }
5159
5160
    /**
5161
     * Sets the prerequisite of a LP (and save)
5162
     * @param	int		integer giving the new prerequisite of this learnpath
5163
     * @return 	bool 	returns true if prerequisite is not empty
5164
     */
5165
    public function set_prerequisite($prerequisite)
5166
    {
5167
        if ($this->debug > 0) {
5168
            error_log('New LP - In learnpath::set_prerequisite()', 0);
5169
        }
5170
        $this->prerequisite = intval($prerequisite);
5171
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5172
        $lp_id = $this->get_id();
5173
        $sql = "UPDATE $lp_table SET prerequisite = '".$this->prerequisite."'
5174
                WHERE iid = $lp_id";
5175
        if ($this->debug > 2) {
5176
            error_log('New LP - lp updated with new preview requisite : '.$this->requisite, 0);
0 ignored issues
show
Bug introduced by
The property requisite does not exist on learnpath. Did you mean prerequisite?
Loading history...
5177
        }
5178
        Database::query($sql);
5179
        return true;
5180
    }
5181
5182
    /**
5183
     * Sets the location/proximity of the LP (local/remote) (and save)
5184
     * @param	string	Optional string giving the new location of this learnpath
5185
     * @return  boolean True on success / False on error
5186
     */
5187
    public function set_proximity($name = '')
5188
    {
5189
        if ($this->debug > 0) {
5190
            error_log('New LP - In learnpath::set_proximity()', 0);
5191
        }
5192
        if (empty ($name))
5193
            return false;
5194
5195
        $this->proximity = $name;
5196
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5197
        $lp_id = $this->get_id();
5198
        $sql = "UPDATE $lp_table SET
5199
                    content_local = '".Database::escape_string($name)."'
5200
                WHERE iid = $lp_id";
5201
        if ($this->debug > 2) {
5202
            error_log('New LP - lp updated with new proximity : '.$this->proximity, 0);
5203
        }
5204
        Database::query($sql);
5205
        return true;
5206
    }
5207
5208
    /**
5209
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item
5210
     * @param	integer	DB ID of the item
5211
     */
5212
    public function set_previous_item($id)
5213
    {
5214
        if ($this->debug > 0) {
5215
            error_log('New LP - In learnpath::set_previous_item()', 0);
5216
        }
5217
        $this->last = $id;
5218
    }
5219
5220
    /**
5221
     * Sets use_max_score
5222
     * @param   int  $use_max_score Optional string giving the new location of this learnpath
5223
     * @return  boolean True on success / False on error
5224
     */
5225
    public function set_use_max_score($use_max_score = 1)
5226
    {
5227
        if ($this->debug > 0) {
5228
            error_log('New LP - In learnpath::set_use_max_score()', 0);
5229
        }
5230
        $use_max_score = intval($use_max_score);
5231
        $this->use_max_score = $use_max_score;
5232
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5233
        $lp_id = $this->get_id();
5234
        $sql = "UPDATE $lp_table SET
5235
                    use_max_score = '".$this->use_max_score."'
5236
                WHERE iid = $lp_id";
5237
5238
        if ($this->debug > 2) {
5239
            error_log('New LP - lp updated with new use_max_score : '.$this->use_max_score, 0);
5240
        }
5241
        Database::query($sql);
5242
5243
        return true;
5244
    }
5245
5246
    /**
5247
     * Sets and saves the expired_on date
5248
     * @param   string  $expired_on Optional string giving the new author of this learnpath
5249
     * @return   bool    Returns true if author's name is not empty
5250
     */
5251
    public function set_expired_on($expired_on)
5252
    {
5253
        if ($this->debug > 0) {
5254
            error_log('New LP - In learnpath::set_expired_on()', 0);
5255
        }
5256
5257
        $em = Database::getManager();
5258
        /** @var CLp $lp */
5259
        $lp = $em
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $lp is correct as $em->getRepository('Cham...d' => $this->get_id())) targeting Doctrine\ORM\EntityRepository::findOneBy() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
5260
            ->getRepository('ChamiloCourseBundle:CLp')
5261
            ->findOneBy(
5262
                [
5263
                    'iid' => $this->get_id()
5264
                ]
5265
            );
5266
5267
        if (!$lp) {
5268
            return false;
5269
        }
5270
5271
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5272
5273
        $lp->setExpiredOn($this->expired_on);
5274
        $em->persist($lp);
5275
        $em->flush();
5276
5277
        if ($this->debug > 2) {
5278
            error_log('New LP - lp updated with new expired_on : '.$this->expired_on, 0);
5279
        }
5280
5281
        return true;
5282
    }
5283
5284
    /**
5285
     * Sets and saves the publicated_on date
5286
     * @param   string  $publicated_on Optional string giving the new author of this learnpath
5287
     * @return   bool    Returns true if author's name is not empty
5288
     */
5289
    public function set_publicated_on($publicated_on)
5290
    {
5291
        if ($this->debug > 0) {
5292
            error_log('New LP - In learnpath::set_expired_on()', 0);
5293
        }
5294
5295
        $em = Database::getManager();
5296
        /** @var CLp $lp */
5297
        $lp = $em
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $lp is correct as $em->getRepository('Cham...d' => $this->get_id())) targeting Doctrine\ORM\EntityRepository::findOneBy() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
5298
            ->getRepository('ChamiloCourseBundle:CLp')
5299
            ->findOneBy(
5300
                [
5301
                    'iid' => $this->get_id()
5302
                ]
5303
            );
5304
5305
        if (!$lp) {
5306
            return false;
5307
        }
5308
5309
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5310
        $lp->setPublicatedOn($this->publicated_on);
5311
        $em->persist($lp);
5312
        $em->flush();
5313
5314
        if ($this->debug > 2) {
5315
            error_log('New LP - lp updated with new publicated_on : '.$this->publicated_on, 0);
5316
        }
5317
5318
        return true;
5319
    }
5320
5321
    /**
5322
     * Sets and saves the expired_on date
5323
     * @return   bool    Returns true if author's name is not empty
5324
     */
5325
    public function set_modified_on()
5326
    {
5327
        if ($this->debug > 0) {
5328
            error_log('New LP - In learnpath::set_expired_on()', 0);
5329
        }
5330
        $this->modified_on = api_get_utc_datetime();
5331
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5332
        $lp_id = $this->get_id();
5333
        $sql = "UPDATE $lp_table SET modified_on = '".$this->modified_on."'
5334
                WHERE iid = $lp_id";
5335
        if ($this->debug > 2) {
5336
            error_log('New LP - lp updated with new expired_on : '.$this->modified_on, 0);
5337
        }
5338
        Database::query($sql);
5339
5340
        return true;
5341
    }
5342
5343
    /**
5344
     * Sets the object's error message
5345
     * @param	string	Error message. If empty, reinits the error string
5346
     * @return 	void
5347
     */
5348
    public function set_error_msg($error = '')
5349
    {
5350
        if ($this->debug > 0) {
5351
            error_log('New LP - In learnpath::set_error_msg()', 0);
5352
        }
5353
        if (empty ($error)) {
5354
            $this->error = '';
5355
        } else {
5356
            $this->error .= $error;
5357
        }
5358
    }
5359
5360
    /**
5361
     * Launches the current item if not 'sco'
5362
     * (starts timer and make sure there is a record ready in the DB)
5363
     * @param  boolean  $allow_new_attempt Whether to allow a new attempt or not
5364
     * @return boolean
5365
     */
5366
    public function start_current_item($allow_new_attempt = false)
5367
    {
5368
        if ($this->debug > 0) {
5369
            error_log('New LP - In learnpath::start_current_item()', 0);
5370
        }
5371
        if ($this->current != 0 && is_object($this->items[$this->current])) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->current of type null|mixed to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
5372
            $type = $this->get_type();
5373
            $item_type = $this->items[$this->current]->get_type();
5374
            if (($type == 2 && $item_type != 'sco') ||
5375
                ($type == 3 && $item_type != 'au') ||
5376
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5377
            ) {
5378
                $this->items[$this->current]->open($allow_new_attempt);
5379
                $this->autocomplete_parents($this->current);
5380
                $prereq_check = $this->prerequisites_match($this->current);
5381
                $this->items[$this->current]->save(false, $prereq_check);
5382
                //$this->update_queue[$this->last] = $this->items[$this->last]->get_status();
5383
            }
5384
            // If sco, then it is supposed to have been updated by some other call.
5385
            if ($item_type == 'sco') {
5386
                $this->items[$this->current]->restart();
5387
            }
5388
        }
5389
        if ($this->debug > 0) {
5390
            error_log('New LP - End of learnpath::start_current_item()', 0);
5391
        }
5392
        return true;
5393
    }
5394
5395
    /**
5396
     * Stops the processing and counters for the old item (as held in $this->last)
5397
     * @return boolean  True/False
5398
     */
5399
    public function stop_previous_item()
5400
    {
5401
        if ($this->debug > 0) {
5402
            error_log('New LP - In learnpath::stop_previous_item()', 0);
5403
        }
5404
5405
        if ($this->last != 0 && $this->last != $this->current && is_object($this->items[$this->last])) {
5406
            if ($this->debug > 2) {
5407
                error_log('New LP - In learnpath::stop_previous_item() - '.$this->last.' is object', 0);
0 ignored issues
show
Bug introduced by
Are you sure $this->last of type mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5407
                error_log('New LP - In learnpath::stop_previous_item() - './** @scrutinizer ignore-type */ $this->last.' is object', 0);
Loading history...
5408
            }
5409
            switch ($this->get_type()) {
5410
                case '3':
5411
                    if ($this->items[$this->last]->get_type() != 'au') {
5412
                        if ($this->debug > 2) {
5413
                            error_log('New LP - In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au', 0);
5414
                        }
5415
                        $this->items[$this->last]->close();
5416
                        //$this->autocomplete_parents($this->last);
5417
                        //$this->update_queue[$this->last] = $this->items[$this->last]->get_status();
5418
                    } else {
5419
                        if ($this->debug > 2) {
5420
                            error_log('New LP - In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals', 0);
5421
                        }
5422
                    }
5423
                    break;
5424
                case '2':
5425
                    if ($this->items[$this->last]->get_type() != 'sco') {
5426
                        if ($this->debug > 2) {
5427
                            error_log('New LP - In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco', 0);
5428
                        }
5429
                        $this->items[$this->last]->close();
5430
                        //$this->autocomplete_parents($this->last);
5431
                        //$this->update_queue[$this->last] = $this->items[$this->last]->get_status();
5432
                    } else {
5433
                        if ($this->debug > 2) {
5434
                            error_log('New LP - In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals', 0);
5435
                        }
5436
                    }
5437
                    break;
5438
                case '1':
5439
                default:
5440
                    if ($this->debug > 2) {
5441
                        error_log('New LP - In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset', 0);
5442
                    }
5443
                    $this->items[$this->last]->close();
5444
                    break;
5445
            }
5446
        } else {
5447
            if ($this->debug > 2) {
5448
                error_log('New LP - In learnpath::stop_previous_item() - No previous element found, ignoring...', 0);
5449
            }
5450
            return false;
5451
        }
5452
        return true;
5453
    }
5454
5455
    /**
5456
     * Updates the default view mode from fullscreen to embedded and inversely
5457
     * @return	string The current default view mode ('fullscreen' or 'embedded')
5458
     */
5459
    public function update_default_view_mode()
5460
    {
5461
        if ($this->debug > 0) {
5462
            error_log('New LP - In learnpath::update_default_view_mode()', 0);
5463
        }
5464
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5465
        $sql = "SELECT * FROM $lp_table
5466
                WHERE iid = ".$this->get_id();
5467
        $res = Database::query($sql);
5468
        if (Database::num_rows($res) > 0) {
5469
            $row = Database::fetch_array($res);
5470
            $default_view_mode = $row['default_view_mod'];
5471
            $view_mode = $default_view_mode;
5472
            switch ($default_view_mode) {
5473
                case 'fullscreen': // default with popup
5474
                    $view_mode = 'embedded';
5475
                    break;
5476
                case 'embedded': // default view with left menu
5477
                    $view_mode = 'embedframe';
5478
                    break;
5479
                case 'embedframe': //folded menu
5480
                    $view_mode = 'impress';
5481
                    break;
5482
                case 'impress':
5483
                    $view_mode = 'fullscreen';
5484
                    break;
5485
            }
5486
            $sql = "UPDATE $lp_table SET default_view_mod = '$view_mode'
5487
                    WHERE iid = ".$this->get_id();
5488
            Database::query($sql);
5489
            $this->mode = $view_mode;
5490
5491
            return $view_mode;
5492
        } else {
5493
            if ($this->debug > 2) {
5494
                error_log('New LP - Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5495
            }
5496
        }
5497
        return -1;
5498
    }
5499
5500
    /**
5501
     * Updates the default behaviour about auto-commiting SCORM updates
5502
     * @return	boolean	True if auto-commit has been set to 'on', false otherwise
5503
     */
5504
    public function update_default_scorm_commit()
5505
    {
5506
        if ($this->debug > 0) {
5507
            error_log('New LP - In learnpath::update_default_scorm_commit()', 0);
5508
        }
5509
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5510
        $sql = "SELECT * FROM $lp_table
5511
                WHERE iid = ".$this->get_id();
5512
        $res = Database::query($sql);
5513
        if (Database::num_rows($res) > 0) {
5514
            $row = Database::fetch_array($res);
5515
            $force = $row['force_commit'];
5516
            if ($force == 1) {
5517
                $force = 0;
5518
                $force_return = false;
5519
            } elseif ($force == 0) {
5520
                $force = 1;
5521
                $force_return = true;
5522
            }
5523
            $sql = "UPDATE $lp_table SET force_commit = $force
5524
                    WHERE iid = ".$this->get_id();
5525
            Database::query($sql);
5526
            $this->force_commit = $force_return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $force_return does not seem to be defined for all execution paths leading up to this point.
Loading history...
5527
5528
            return $force_return;
5529
        } else {
5530
            if ($this->debug > 2) {
5531
                error_log('New LP - Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5532
            }
5533
        }
5534
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5535
    }
5536
5537
    /**
5538
     * Updates the order of learning paths (goes through all of them by order and fills the gaps)
5539
     * @return	bool	True on success, false on failure
5540
     */
5541
    public function update_display_order()
5542
    {
5543
        $course_id = api_get_course_int_id();
5544
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5545
5546
        $sql = "SELECT * FROM $lp_table WHERE c_id = $course_id ORDER BY display_order";
5547
        $res = Database::query($sql);
5548
        if ($res === false) {
5549
            return false;
5550
        }
5551
5552
        $num = Database::num_rows($res);
5553
        // First check the order is correct, globally (might be wrong because
5554
        // of versions < 1.8.4).
5555
        if ($num > 0) {
5556
            $i = 1;
5557
            while ($row = Database::fetch_array($res)) {
5558
                if ($row['display_order'] != $i) {
5559
                    // If we find a gap in the order, we need to fix it.
5560
                    $sql = "UPDATE $lp_table SET display_order = $i
5561
                            WHERE iid = ".$row['iid'];
5562
                    Database::query($sql);
5563
                }
5564
                $i++;
5565
            }
5566
        }
5567
        return true;
5568
    }
5569
5570
    /**
5571
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view
5572
     * @return	boolean	True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5573
     */
5574
    public function update_reinit()
5575
    {
5576
        $course_id = api_get_course_int_id();
5577
        if ($this->debug > 0) {
5578
            error_log('New LP - In learnpath::update_reinit()', 0);
5579
        }
5580
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5581
        $sql = "SELECT * FROM $lp_table
5582
                WHERE iid = ".$this->get_id();
5583
        $res = Database::query($sql);
5584
        if (Database::num_rows($res) > 0) {
5585
            $row = Database::fetch_array($res);
5586
            $force = $row['prevent_reinit'];
5587
            if ($force == 1) {
5588
                $force = 0;
5589
            } elseif ($force == 0) {
5590
                $force = 1;
5591
            }
5592
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5593
                    WHERE iid = ".$this->get_id();
5594
            Database::query($sql);
5595
            $this->prevent_reinit = $force;
5596
            return $force;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $force also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
5597
        } else {
5598
            if ($this->debug > 2) {
5599
                error_log('New LP - Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5600
            }
5601
        }
5602
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5603
    }
5604
5605
    /**
5606
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag
5607
     *
5608
     * @return string 'single', 'multi' or 'seriousgame'
5609
     * @author ndiechburg <[email protected]>
5610
     **/
5611
    public function get_attempt_mode()
5612
    {
5613
        //Set default value for seriousgame_mode
5614
        if (!isset($this->seriousgame_mode)) {
5615
            $this->seriousgame_mode = 0;
5616
        }
5617
        // Set default value for prevent_reinit
5618
        if (!isset($this->prevent_reinit)) {
5619
            $this->prevent_reinit = 1;
5620
        }
5621
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5622
            return 'seriousgame';
5623
        }
5624
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5625
            return 'single';
5626
        }
5627
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5628
            return 'multiple';
5629
        }
5630
        return 'single';
5631
    }
5632
5633
    /**
5634
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags
5635
     *
5636
     * @param string 'seriousgame', 'single' or 'multiple'
5637
     * @return boolean
5638
     * @author ndiechburg <[email protected]>
5639
     **/
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...
5640
    public function set_attempt_mode($mode)
5641
    {
5642
        switch ($mode) {
5643
            case 'seriousgame':
5644
                $sg_mode = 1;
5645
                $prevent_reinit = 1;
5646
                break;
5647
            case 'single':
5648
                $sg_mode = 0;
5649
                $prevent_reinit = 1;
5650
                break;
5651
            case 'multiple':
5652
                $sg_mode = 0;
5653
                $prevent_reinit = 0;
5654
                break;
5655
            default:
5656
                $sg_mode = 0;
5657
                $prevent_reinit = 0;
5658
                break;
5659
        }
5660
        $this->prevent_reinit = $prevent_reinit;
5661
        $this->seriousgame_mode = $sg_mode;
5662
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5663
        $sql = "UPDATE $lp_table SET
5664
                prevent_reinit = $prevent_reinit ,
5665
                seriousgame_mode = $sg_mode
5666
                WHERE iid = ".$this->get_id();
5667
        $res = Database::query($sql);
5668
        if ($res) {
5669
            return true;
5670
        } else {
5671
            return false;
5672
        }
5673
    }
5674
5675
    /**
5676
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm)
5677
     *
5678
     * @author ndiechburg <[email protected]>
5679
     **/
5680
    public function switch_attempt_mode()
5681
    {
5682
        if ($this->debug > 0) {
5683
            error_log('New LP - In learnpath::switch_attempt_mode()', 0);
5684
        }
5685
        $mode = $this->get_attempt_mode();
5686
        switch ($mode) {
5687
            case 'single':
5688
                $next_mode = 'multiple';
5689
                break;
5690
            case 'multiple':
5691
                $next_mode = 'seriousgame';
5692
                break;
5693
            case 'seriousgame':
5694
                $next_mode = 'single';
5695
                break;
5696
            default:
5697
                $next_mode = 'single';
5698
                break;
5699
        }
5700
        $this->set_attempt_mode($next_mode);
5701
    }
5702
5703
    /**
5704
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5705
     * but possibility to do again a completed item.
5706
     *
5707
     * @return boolean true if seriousgame_mode has been set to 1, false otherwise
5708
     * @author ndiechburg <[email protected]>
5709
     **/
5710
    public function set_seriousgame_mode()
5711
    {
5712
        if ($this->debug > 0) {
5713
            error_log('New LP - In learnpath::set_seriousgame_mode()', 0);
5714
        }
5715
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5716
        $sql = "SELECT * FROM $lp_table 
5717
                WHERE iid = ".$this->get_id();
5718
        $res = Database::query($sql);
5719
        if (Database::num_rows($res) > 0) {
5720
            $row = Database::fetch_array($res);
5721
            $force = $row['seriousgame_mode'];
5722
            if ($force == 1) {
5723
                $force = 0;
5724
            } elseif ($force == 0) {
5725
                $force = 1;
5726
            }
5727
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5728
			        WHERE iid = ".$this->get_id();
5729
            Database::query($sql);
5730
            $this->seriousgame_mode = $force;
5731
            return $force;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $force also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
5732
        } else {
5733
            if ($this->debug > 2) {
5734
                error_log('New LP - Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
5735
            }
5736
        }
5737
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5738
    }
5739
5740
    /**
5741
     * Updates the "scorm_debug" value that shows or hide the debug window
5742
     * @return	boolean	True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5743
     */
5744
    public function update_scorm_debug()
5745
    {
5746
        if ($this->debug > 0) {
5747
            error_log('New LP - In learnpath::update_scorm_debug()', 0);
5748
        }
5749
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5750
        $sql = "SELECT * FROM $lp_table
5751
                WHERE iid = ".$this->get_id();
5752
        $res = Database::query($sql);
5753
        if (Database::num_rows($res) > 0) {
5754
            $row = Database::fetch_array($res);
5755
            $force = $row['debug'];
5756
            if ($force == 1) {
5757
                $force = 0;
5758
            } elseif ($force == 0) {
5759
                $force = 1;
5760
            }
5761
            $sql = "UPDATE $lp_table SET debug = $force
5762
                    WHERE iid = ".$this->get_id();
5763
            Database::query($sql);
5764
            $this->scorm_debug = $force;
5765
            return $force;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $force also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
5766
        } else {
5767
            if ($this->debug > 2) {
5768
                error_log('New LP - Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
5769
            }
5770
        }
5771
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5772
    }
5773
5774
    /**
5775
     * Function that makes a call to the function sort_tree_array and create_tree_array
5776
     * @author Kevin Van Den Haute
5777
     * @param  array
5778
     */
5779
    public function tree_array($array)
5780
    {
5781
        if ($this->debug > 1) {
5782
            error_log('New LP - In learnpath::tree_array()', 0);
5783
        }
5784
        $array = $this->sort_tree_array($array);
5785
        $this->create_tree_array($array);
5786
    }
5787
5788
    /**
5789
     * Creates an array with the elements of the learning path tree in it
5790
     *
5791
     * @author Kevin Van Den Haute
5792
     * @param array $array
5793
     * @param int $parent
5794
     * @param int $depth
5795
     * @param array $tmp
5796
     */
5797
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5798
    {
5799
        if ($this->debug > 1) {
5800
            error_log('New LP - In learnpath::create_tree_array())', 0);
5801
        }
5802
5803
        if (is_array($array)) {
5804
            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...
5805
                if ($array[$i]['parent_item_id'] == $parent) {
5806
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5807
                        $tmp[] = $array[$i]['parent_item_id'];
5808
                        $depth++;
5809
                    }
5810
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5811
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5812
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5813
5814
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5815
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5816
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5817
                    $this->arrMenu[] = array(
5818
                        'id' => $array[$i]['id'],
5819
                        'ref' => $ref,
5820
                        'item_type' => $array[$i]['item_type'],
5821
                        'title' => $array[$i]['title'],
5822
                        'path' => $path,
5823
                        'description' => $array[$i]['description'],
5824
                        'parent_item_id' => $array[$i]['parent_item_id'],
5825
                        'previous_item_id' => $array[$i]['previous_item_id'],
5826
                        'next_item_id' => $array[$i]['next_item_id'],
5827
                        'min_score' => $array[$i]['min_score'],
5828
                        'max_score' => $array[$i]['max_score'],
5829
                        'mastery_score' => $array[$i]['mastery_score'],
5830
                        'display_order' => $array[$i]['display_order'],
5831
                        'prerequisite' => $preq,
5832
                        'depth' => $depth,
5833
                        'audio' => $audio,
5834
                        'prerequisite_min_score' => $prerequisiteMinScore,
5835
                        'prerequisite_max_score' => $prerequisiteMaxScore
5836
                    );
5837
5838
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5839
                }
5840
            }
5841
        }
5842
    }
5843
5844
    /**
5845
     * Sorts a multi dimensional array by parent id and display order
5846
     * @author Kevin Van Den Haute
5847
     *
5848
     * @param array $array (array with al the learning path items in it)
5849
     *
5850
     * @return array
5851
     */
5852
    public function sort_tree_array($array)
5853
    {
5854
        foreach ($array as $key => $row) {
5855
            $parent[$key] = $row['parent_item_id'];
5856
            $position[$key] = $row['display_order'];
5857
        }
5858
5859
        if (count($array) > 0) {
5860
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $position seems to be defined by a foreach iteration on line 5854. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
5861
        }
5862
5863
        return $array;
5864
    }
5865
5866
    /**
5867
     * Function that creates a html list of learning path items so that we can add audio files to them
5868
     * @author Kevin Van Den Haute
5869
     * @return string
5870
     */
5871
    public function overview()
5872
    {
5873
        if ($this->debug > 0) {
5874
            error_log('New LP - In learnpath::overview()', 0);
5875
        }
5876
5877
        $return = '';
5878
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5879
5880
        // we need to start a form when we want to update all the mp3 files
5881
        if ($update_audio == 'true') {
5882
            $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">';
5883
        }
5884
        $return .= '<div id="message"></div>';
5885
        if (count($this->items) == 0) {
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type mixed; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5885
        if (count(/** @scrutinizer ignore-type */ $this->items) == 0) {
Loading history...
5886
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5887
        } else {
5888
            $return_audio = '<table class="data_table">';
5889
            $return_audio .= '<tr>';
5890
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5891
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5892
            $return_audio .= '</tr>';
5893
5894
            if ($update_audio != 'true') {
5895
                $return .= '<div class="col-md-12">';
5896
                $return .= self::return_new_tree($update_audio);
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::return_new_tree() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

5896
                $return .= self::/** @scrutinizer ignore-call */ return_new_tree($update_audio);
Loading history...
5897
                $return .= '</div>';
5898
                $return .= Display::div(
5899
                    Display::url(get_lang('Save'), '#', array('id' => 'listSubmit', 'class' => 'btn btn-primary')),
5900
                    array('style' => 'float:left; margin-top:15px;width:100%')
5901
                );
5902
            } else {
5903
                $return_audio .= self::return_new_tree($update_audio);
5904
                $return .= $return_audio.'</table>';
5905
            }
5906
5907
            // We need to close the form when we are updating the mp3 files.
5908
            if ($update_audio == 'true') {
5909
                $return .= '<div class="footer-audio">';
5910
                $return .= Display::button(
5911
                    'save_audio',
5912
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5913
                    array('class' => 'btn btn-primary', 'type' => 'submit')
5914
                );
5915
                $return .= '</div>';
5916
            }
5917
        }
5918
5919
        // We need to close the form when we are updating the mp3 files.
5920
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5921
            $return .= '</form>';
5922
        }
5923
5924
        return $return;
5925
    }
5926
5927
    /**
5928
     * @param string string $update_audio
5929
     * @param bool $drop_element_here
5930
     * @return string
5931
     */
5932
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
5933
    {
5934
        $return = '';
5935
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5936
        $course_id = api_get_course_int_id();
5937
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
5938
5939
        $sql = "SELECT * FROM $tbl_lp_item
5940
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
5941
5942
        $result = Database::query($sql);
5943
        $arrLP = [];
5944
        while ($row = Database::fetch_array($result)) {
5945
5946
            $arrLP[] = array(
5947
                'id' => $row['id'],
5948
                'item_type' => $row['item_type'],
5949
                'title' => Security::remove_XSS($row['title']),
5950
                'path' => $row['path'],
5951
                'description' => Security::remove_XSS($row['description']),
5952
                'parent_item_id' => $row['parent_item_id'],
5953
                'previous_item_id' => $row['previous_item_id'],
5954
                'next_item_id' => $row['next_item_id'],
5955
                'max_score' => $row['max_score'],
5956
                'min_score' => $row['min_score'],
5957
                'mastery_score' => $row['mastery_score'],
5958
                'prerequisite' => $row['prerequisite'],
5959
                'display_order' => $row['display_order'],
5960
                'audio' => $row['audio'],
5961
                'prerequisite_max_score' => $row['prerequisite_max_score'],
5962
                'prerequisite_min_score' => $row['prerequisite_min_score']
5963
            );
5964
        }
5965
5966
        $this->tree_array($arrLP);
5967
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
5968
        unset($this->arrMenu);
5969
        $default_data = null;
5970
        $default_content = null;
5971
        $elements = [];
5972
        $return_audio = null;
5973
5974
        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...
5975
            $title = $arrLP[$i]['title'];
5976
            $title_cut = cut($arrLP[$i]['title'], 25);
5977
5978
            // Link for the documents
5979
            if ($arrLP[$i]['item_type'] == 'document') {
5980
                $url = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5981
                $title_cut = Display::url(
5982
                    $title_cut,
5983
                    $url,
5984
                    array(
5985
                        'class' => 'ajax moved',
5986
                        'data-title' => $title,
5987
                        'title' => $title
5988
                    )
5989
                );
5990
            }
5991
5992
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5993
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5994
                Session::write('pathItem', $arrLP[$i]['path']);
5995
            }
5996
5997
            if (($i % 2) == 0) {
5998
                $oddClass = 'row_odd';
5999
            } else {
6000
                $oddClass = 'row_even';
6001
            }
6002
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6003
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6004
6005
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
6006
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6007
            } else {
6008
                if (file_exists('../img/lp_'.$icon_name.'.gif')) {
6009
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6010
                } else {
6011
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6012
                        $icon = Display::return_icon('certificate.png');
6013
                    } else {
6014
                        $icon = Display::return_icon('folder_document.gif');
6015
                    }
6016
                }
6017
            }
6018
6019
            // The audio column.
6020
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6021
            $audio = '';
6022
            if (!$update_audio || $update_audio <> 'true') {
6023
                if (empty($arrLP[$i]['audio'])) {
6024
                    $audio .= '';
6025
                }
6026
            } else {
6027
                $types = self::getChapterTypes();
6028
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6029
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6030
                    if (!empty ($arrLP[$i]['audio'])) {
6031
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6032
                        <input type="checkbox" name="removemp3' . $arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6033
                    }
6034
                }
6035
            }
6036
6037
            $return_audio .= Display::span($icon.' '.$title).
6038
            Display::tag(
6039
                'td',
6040
                $audio,
6041
                array('style' => '')
6042
            );
6043
            $return_audio .= '</td>';
6044
            $move_icon = '';
6045
            $move_item_icon = '';
6046
            $edit_icon = '';
6047
            $delete_icon = '';
6048
            $audio_icon = '';
6049
            $prerequisities_icon = '';
6050
            $forumIcon = '';
6051
            $previewIcon = '';
6052
6053
            if ($is_allowed_to_edit) {
6054
                if (!$update_audio || $update_audio <> 'true') {
6055
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6056
                        $move_icon .= '<a class="moved" href="#">';
6057
                        $move_icon .= Display::return_icon(
6058
                            'move_everywhere.png',
6059
                            get_lang('Move'),
6060
                            [],
6061
                            ICON_SIZE_TINY
6062
                        );
6063
                        $move_icon .= '</a>';
6064
                    }
6065
                }
6066
6067
                // No edit for this item types
6068
                if (!in_array($arrLP[$i]['item_type'], array('sco', 'asset', 'final_item'))) {
6069
                    if ($arrLP[$i]['item_type'] != 'dir') {
6070
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6071
                        $edit_icon .= Display::return_icon(
6072
                            'edit.png',
6073
                            get_lang('LearnpathEditModule'),
6074
                            [],
6075
                            ICON_SIZE_TINY
6076
                        );
6077
                        $edit_icon .= '</a>';
6078
6079
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6080
                            $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6081
                                $this->course_int_id,
6082
                                $this->lp_session_id
6083
                            );
6084
                            if ($forumThread) {
6085
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6086
                                    'action' => 'dissociate_forum',
6087
                                    'id' => $arrLP[$i]['id'],
6088
                                    'lp_id' => $this->lp_id
6089
                                ]);
6090
                                $forumIcon = Display::url(
6091
                                    Display::return_icon('forum.png', get_lang('DissociateForumToLPItem'), [], ICON_SIZE_TINY),
6092
                                    $forumIconUrl,
6093
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6094
                                );
6095
                            } else {
6096
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6097
                                    'action' => 'create_forum',
6098
                                    'id' => $arrLP[$i]['id'],
6099
                                    'lp_id' => $this->lp_id
6100
                                ]);
6101
                                $forumIcon = Display::url(
6102
                                    Display::return_icon('forum.png', get_lang('AssociateForumToLPItem'), [], ICON_SIZE_TINY),
6103
                                    $forumIconUrl,
6104
                                    ['class' => "btn btn-default lp-btn-associate-forum"]
6105
                                );
6106
                            }
6107
                        }
6108
                    } else {
6109
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6110
                        $edit_icon .= Display::return_icon(
6111
                            'edit.png',
6112
                            get_lang('LearnpathEditModule'),
6113
                            [],
6114
                            ICON_SIZE_TINY
6115
                        );
6116
                        $edit_icon .= '</a>';
6117
                    }
6118
                } else {
6119
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6120
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6121
                        $edit_icon .= Display::return_icon(
6122
                            'edit.png',
6123
                            get_lang('Edit'),
6124
                            [],
6125
                            ICON_SIZE_TINY
6126
                        );
6127
                        $edit_icon .= '</a>';
6128
                    }
6129
                }
6130
6131
                $delete_icon .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" onclick="return confirmation(\''.addslashes($title).'\');" class="btn btn-default">';
6132
                $delete_icon .= Display::return_icon(
6133
                    'delete.png',
6134
                    get_lang('LearnpathDeleteModule'),
6135
                    [],
6136
                    ICON_SIZE_TINY
6137
                );
6138
                $delete_icon .= '</a>';
6139
6140
                $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6141
                $previewImage = Display::return_icon(
6142
                    'preview_view.png',
6143
                    get_lang('Preview'),
6144
                    [],
6145
                    ICON_SIZE_TINY
6146
                );
6147
6148
                switch ($arrLP[$i]['item_type']) {
6149
                    case TOOL_DOCUMENT:
6150
                    case TOOL_LP_FINAL_ITEM:
6151
                        $urlPreviewLink = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6152
                        $previewIcon = Display::url(
6153
                            $previewImage,
6154
                            $urlPreviewLink,
6155
                            array(
6156
                                'target' => '_blank',
6157
                                'class' => 'btn btn-default',
6158
                                'data-title' => $arrLP[$i]['title'],
6159
                                'title' => $arrLP[$i]['title']
6160
                            )
6161
                        );
6162
                        break;
6163
                    case TOOL_THREAD:
6164
                    case TOOL_FORUM:
6165
                    case TOOL_QUIZ:
6166
                    case TOOL_STUDENTPUBLICATION:
6167
                    case TOOL_LP_FINAL_ITEM:
6168
                    case TOOL_LINK:
6169
                        //$target = '';
6170
                        //$class = 'btn btn-default ajax';
6171
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6172
                        $class = 'btn btn-default';
6173
                        $target = '_blank';
6174
                        //}
6175
6176
                        $link = self::rl_get_resource_link_for_learnpath(
6177
                            $this->course_int_id,
6178
                            $this->lp_id,
6179
                            $arrLP[$i]['id'],
6180
                            0,
6181
                            ''
6182
                        );
6183
                        $previewIcon = Display::url(
6184
                            $previewImage,
6185
                            $link,
6186
                            [
6187
                                'class' => $class,
6188
                                'data-title' => $arrLP[$i]['title'],
6189
                                'title' => $arrLP[$i]['title'],
6190
                                'target' => $target
6191
                            ]
6192
                        );
6193
                        break;
6194
                    default:
6195
                        $previewIcon = Display::url(
6196
                            $previewImage,
6197
                            $url.'&action=view_item',
6198
                            ['class' => 'btn btn-default', 'target' => '_blank']
6199
                        );
6200
                        break;
6201
                }
6202
6203
                if ($arrLP[$i]['item_type'] != 'dir') {
6204
                    $prerequisities_icon = Display::url(
6205
                        Display::return_icon(
6206
                            'accept.png',
6207
                            get_lang('LearnpathPrerequisites'),
6208
                            [],
6209
                            ICON_SIZE_TINY
6210
                        ),
6211
                        $url.'&action=edit_item_prereq', ['class' => 'btn btn-default']
6212
                    );
6213
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6214
                        $move_item_icon = Display::url(
6215
                            Display::return_icon(
6216
                                'move.png',
6217
                                get_lang('Move'),
6218
                                [],
6219
                                ICON_SIZE_TINY
6220
                            ),
6221
                            $url.'&action=move_item',
6222
                            ['class' => 'btn btn-default']
6223
                        );
6224
                    }
6225
                    $audio_icon = Display::url(
6226
                        Display::return_icon(
6227
                            'audio.png',
6228
                            get_lang('UplUpload'),
6229
                            [],
6230
                            ICON_SIZE_TINY
6231
                        ),
6232
                        $url.'&action=add_audio',
6233
                        ['class' => 'btn btn-default']
6234
                    );
6235
                }
6236
            }
6237
            if ($update_audio != 'true') {
6238
                $row = $move_icon.' '.$icon.
6239
                    Display::span($title_cut).
6240
                    Display::tag(
6241
                        'div',
6242
                        "<div class=\"btn-group btn-group-xs\">$previewIcon $audio $edit_icon $forumIcon $prerequisities_icon $move_item_icon $audio_icon $delete_icon</div>",
6243
                        array('class'=>'btn-toolbar button_actions')
6244
                    );
6245
            } else {
6246
                $row = Display::span($title.$icon).Display::span($audio, array('class'=>'button_actions'));
6247
            }
6248
6249
            $parent_id = $arrLP[$i]['parent_item_id'];
6250
            $default_data[$arrLP[$i]['id']] = $row;
6251
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6252
6253
            if (empty($parent_id)) {
6254
                $elements[$arrLP[$i]['id']]['data'] = $row;
6255
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6256
            } else {
6257
                $parent_arrays = [];
6258
                if ($arrLP[$i]['depth'] > 1) {
6259
                    //Getting list of parents
6260
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6261
                        foreach ($arrLP as $item) {
6262
                            if ($item['id'] == $parent_id) {
6263
                                if ($item['parent_item_id'] == 0) {
6264
                                    $parent_id = $item['id'];
6265
                                    break;
6266
                                } else {
6267
                                    $parent_id = $item['parent_item_id'];
6268
                                    if (empty($parent_arrays)) {
6269
                                        $parent_arrays[] = intval($item['id']);
6270
                                    }
6271
                                    $parent_arrays[] = $parent_id;
6272
                                    break;
6273
                                }
6274
                            }
6275
                        }
6276
                    }
6277
                }
6278
6279
                if (!empty($parent_arrays)) {
6280
                    $parent_arrays = array_reverse($parent_arrays);
6281
                    $val = '$elements';
6282
                    $x = 0;
6283
                    foreach ($parent_arrays as $item) {
6284
                        if ($x != count($parent_arrays) - 1) {
6285
                            $val .= '["'.$item.'"]["children"]';
6286
                        } else {
6287
                            $val .= '["'.$item.'"]["children"]';
6288
                        }
6289
                        $x++;
6290
                    }
6291
                    $val .= "";
6292
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6293
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6294
                } else {
6295
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6296
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6297
                }
6298
            }
6299
        }
6300
6301
        $list = '<ul id="lp_item_list">';
6302
        $tree = self::print_recursive(
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::print_recursive() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6302
        /** @scrutinizer ignore-call */ 
6303
        $tree = self::print_recursive(
Loading history...
6303
            $elements,
6304
            $default_data,
6305
            $default_content
6306
        );
6307
6308
        if (!empty($tree)) {
6309
            $list .= $tree;
6310
        } else {
6311
            if ($drop_element_here) {
6312
                $list .= Display::return_message(get_lang("DragAndDropAnElementHere"));
6313
            }
6314
        }
6315
        $list .= '</ul>';
6316
6317
        $return .= Display::panelCollapse(
6318
            $this->name,
6319
            $list,
6320
            'scorm-list',
6321
            null,
6322
            'scorm-list-accordion',
6323
            'scorm-list-collapse'
6324
        );
6325
6326
        if ($update_audio == 'true') {
6327
            $return = $return_audio;
6328
        }
6329
6330
        return $return;
6331
    }
6332
6333
    /**
6334
     * @param array $elements
6335
     * @param array $default_data
6336
     * @param array $default_content
6337
     * @return string
6338
     */
6339
    public function print_recursive($elements, $default_data, $default_content)
6340
    {
6341
        $return = '';
6342
        foreach ($elements as $key => $item) {
6343
            if (isset($item['load_data']) || empty($item['data'])) {
6344
                $item['data'] = $default_data[$item['load_data']];
6345
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6346
            }
6347
            $sub_list = '';
6348
            if (isset($item['type']) && $item['type'] == 'dir') {
6349
                $sub_list = Display::tag('li', '', array('class'=>'sub_item empty')); // empty value
6350
            }
6351
            if (empty($item['children'])) {
6352
                $sub_list = Display::tag('ul', $sub_list, array('id'=>'UL_'.$key, 'class'=>'record li_container'));
6353
                $active = null;
6354
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6355
                    $active = 'active';
6356
                }
6357
                $return .= Display::tag(
6358
                    'li',
6359
                    Display::div($item['data'], array('class'=>"item_data $active")).$sub_list,
6360
                    array('id'=>$key, 'class'=>'record li_container')
6361
                );
6362
            } else {
6363
                // Sections
6364
                if (isset($item['children'])) {
6365
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::print_recursive() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6365
                    /** @scrutinizer ignore-call */ 
6366
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
Loading history...
6366
                }
6367
                $sub_list = Display::tag('ul', $sub_list.$data, array('id'=>'UL_'.$key, 'class'=>'record li_container'));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
6368
                $return .= Display::tag(
6369
                    'li',
6370
                    Display::div($item['data'], array('class'=>'item_data')).$sub_list,
6371
                    array('id'=>$key, 'class'=>'record li_container')
6372
                );
6373
            }
6374
        }
6375
6376
        return $return;
6377
    }
6378
6379
    /**
6380
     * This function builds the action menu
6381
     * @param bool $returnContent Optional
6382
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6383
     * @param bool $isConfigPage Optional. If is the config page, show the edit button
6384
     * @param bool $allowExpand Optional. Allow show the expand/contract button
6385
     * @return string
6386
     */
6387
    public function build_action_menu(
6388
        $returnContent = false,
6389
        $showRequirementButtons = true,
6390
        $isConfigPage = false,
6391
        $allowExpand = true
6392
    ) {
6393
        $actionsLeft = '';
6394
        $actionsRight = '';
6395
6396
        $actionsLeft .= Display::url(
6397
            Display::return_icon(
6398
                'preview_view.png',
6399
                get_lang('Preview'),
6400
                '',
6401
                ICON_SIZE_MEDIUM
6402
            ),
6403
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6404
                'action' => 'view',
6405
                'lp_id' => $this->lp_id,
6406
                'isStudentView' => 'true'
6407
            ])
6408
        );
6409
        $actionsLeft .= Display::url(
6410
            Display::return_icon(
6411
                'upload_audio.png',
6412
                get_lang('UpdateAllAudioFragments'),
6413
                '',
6414
                ICON_SIZE_MEDIUM
6415
            ),
6416
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6417
                'action' => 'admin_view',
6418
                'lp_id' => $this->lp_id,
6419
                'updateaudio' => 'true'
6420
            ])
6421
        );
6422
6423
        if (!$isConfigPage) {
6424
            $actionsLeft .= Display::url(
6425
                Display::return_icon(
6426
                    'settings.png',
6427
                    get_lang('CourseSettings'),
6428
                    '',
6429
                    ICON_SIZE_MEDIUM
6430
                ),
6431
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6432
                    'action' => 'edit',
6433
                    'lp_id' => $this->lp_id
6434
                ])
6435
            );
6436
        } else {
6437
            $actionsLeft .= Display::url(
6438
                Display::return_icon(
6439
                    'edit.png',
6440
                    get_lang('Edit'),
6441
                    '',
6442
                    ICON_SIZE_MEDIUM
6443
                ),
6444
                'lp_controller.php?'.http_build_query([
6445
                    'action' => 'build',
6446
                    'lp_id' => $this->lp_id
6447
                ]).'&'.api_get_cidreq()
6448
            );
6449
        }
6450
6451
        if ($allowExpand) {
6452
            $actionsLeft .= Display::url(
6453
                Display::return_icon(
6454
                    'expand.png',
6455
                    get_lang('Expand'),
6456
                    array('id' => 'expand'),
6457
                    ICON_SIZE_MEDIUM
6458
                ).
6459
                Display::return_icon(
6460
                    'contract.png',
6461
                    get_lang('Collapse'),
6462
                    array('id' => 'contract', 'class' => 'hide'),
6463
                    ICON_SIZE_MEDIUM
6464
                ),
6465
                '#',
6466
                ['role' => 'button', 'id' => 'hide_bar_template']
6467
            );
6468
        }
6469
6470
        if ($showRequirementButtons) {
6471
            $buttons = array(
6472
                array(
6473
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6474
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6475
                        'action' => 'set_previous_step_as_prerequisite',
6476
                        'lp_id' => $this->lp_id
6477
                    ])
6478
                ),
6479
                array(
6480
                    'title' => get_lang('ClearAllPrerequisites'),
6481
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6482
                        'action' => 'clear_prerequisites',
6483
                        'lp_id' => $this->lp_id
6484
                    ])
6485
                ),
6486
            );
6487
            $actionsRight = Display::groupButtonWithDropDown(
6488
                get_lang('PrerequisitesOptions'),
6489
                $buttons,
6490
                true
6491
            );
6492
        }
6493
6494
        $toolbar = Display::toolbarAction(
6495
            'actions-lp-controller',
6496
            [$actionsLeft, $actionsRight]
6497
        );
6498
6499
        if ($returnContent) {
6500
6501
            return $toolbar;
6502
        }
6503
6504
        echo $toolbar;
6505
    }
6506
6507
    /**
6508
     * Creates the default learning path folder
6509
     * @param array $course
6510
     * @param int $creatorId
6511
     *
6512
     * @return bool
6513
     */
6514
    public static function generate_learning_path_folder($course, $creatorId = 0)
6515
    {
6516
        // Creating learning_path folder
6517
        $dir = '/learning_path';
6518
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6519
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6520
6521
        $folder = false;
6522
        if (!is_dir($filepath.'/'.$dir)) {
6523
            $folderData = create_unexisting_directory(
6524
                $course,
6525
                $creatorId,
6526
                0,
6527
                0,
6528
                0,
6529
                $filepath,
6530
                $dir,
6531
                get_lang('LearningPaths'),
6532
                0
6533
            );
6534
            if (!empty($folderData)) {
6535
                $folder = true;
6536
            }
6537
        } else {
6538
            $folder = true;
6539
        }
6540
6541
        return $folder;
6542
    }
6543
6544
    /**
6545
     * @param array $course
6546
     * @param string $lp_name
6547
     * @param int $creatorId
6548
     *
6549
     * @return array
6550
     */
6551
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6552
    {
6553
        $filepath = '';
6554
        $dir = '/learning_path/';
6555
6556
        if (empty($lp_name)) {
6557
            $lp_name = $this->name;
6558
        }
6559
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6560
6561
        $folder = self::generate_learning_path_folder($course, $creatorId);
6562
6563
        // Limits title size
6564
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6565
        $dir = $dir.$title;
6566
6567
        // Creating LP folder
6568
        $documentId = null;
6569
6570
        if ($folder) {
6571
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6572
            if (!is_dir($filepath.'/'.$dir)) {
6573
                $folderData = create_unexisting_directory(
6574
                    $course,
6575
                    $creatorId,
6576
                    0,
6577
                    0,
6578
                    0,
6579
                    $filepath,
6580
                    $dir,
6581
                    $lp_name
6582
                );
6583
                if (!empty($folderData)) {
6584
                    $folder = true;
6585
                }
6586
6587
                $documentId = $folderData['id'];
6588
            } else {
6589
                $folder = true;
6590
            }
6591
            $dir = $dir.'/';
6592
            if ($folder) {
6593
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6594
            }
6595
        }
6596
6597
        if (empty($documentId)) {
6598
            $dir = api_remove_trailing_slash($dir);
6599
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6600
        }
6601
6602
        $array = array(
6603
            'dir' => $dir,
6604
            'filepath' => $filepath,
6605
            'folder' => $folder,
6606
            'id' => $documentId
6607
        );
6608
6609
        return $array;
6610
    }
6611
6612
    /**
6613
     * Create a new document //still needs some finetuning
6614
     * @param array $courseInfo
6615
     * @param string $content
6616
     * @param string $title
6617
     * @param string $extension
6618
     * @param int $parentId
6619
     * @param int $creatorId creator id
6620
     *
6621
     * @return int
6622
     */
6623
    public function create_document(
6624
        $courseInfo,
6625
        $content = '',
6626
        $title = '',
6627
        $extension = 'html',
6628
        $parentId = 0,
6629
        $creatorId = 0
6630
    ) {
6631
        if (!empty($courseInfo)) {
6632
            $course_id = $courseInfo['real_id'];
6633
        } else {
6634
            $course_id = api_get_course_int_id();
6635
        }
6636
6637
       $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6638
       $sessionId = api_get_session_id();
6639
6640
        // Generates folder
6641
        $result = $this->generate_lp_folder($courseInfo);
6642
        $dir = $result['dir'];
6643
6644
        if (empty($parentId) || $parentId == '/') {
6645
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6646
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6647
6648
            if ($parentId === '/') {
6649
                $dir = '/';
6650
            }
6651
6652
            // Please, do not modify this dirname formatting.
6653
            if (strstr($dir, '..')) {
6654
                $dir = '/';
6655
            }
6656
6657
            if (!empty($dir[0]) && $dir[0] == '.') {
6658
                $dir = substr($dir, 1);
6659
            }
6660
            if (!empty($dir[0]) && $dir[0] != '/') {
6661
                $dir = '/'.$dir;
6662
            }
6663
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6664
                $dir .= '/';
6665
            }
6666
        } else {
6667
            $parentInfo = DocumentManager::get_document_data_by_id(
6668
                $parentId,
6669
                $courseInfo['code']
6670
            );
6671
            if (!empty($parentInfo)) {
6672
                $dir = $parentInfo['path'].'/';
6673
            }
6674
        }
6675
6676
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6677
        if (!is_dir($filepath)) {
6678
            $dir = '/';
6679
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6680
        }
6681
6682
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6683
        // is already escaped twice when it gets here.
6684
6685
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6686
        if (!empty($title)) {
6687
            $title = api_replace_dangerous_char(stripslashes($title));
6688
        } else {
6689
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6690
        }
6691
6692
        $title = disable_dangerous_file($title);
6693
        $filename = $title;
6694
        $content = !empty($content) ? $content : $_POST['content_lp'];
6695
        $tmp_filename = $filename;
6696
6697
        $i = 0;
6698
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6699
            $tmp_filename = $filename.'_'.++ $i;
6700
        }
6701
6702
        $filename = $tmp_filename.'.'.$extension;
6703
        if ($extension == 'html') {
6704
            $content = stripslashes($content);
6705
            $content = str_replace(
6706
                api_get_path(WEB_COURSE_PATH),
6707
                api_get_path(REL_PATH).'courses/',
6708
                $content
6709
            );
6710
6711
            // Change the path of mp3 to absolute.
6712
6713
            // The first regexp deals with :// urls.
6714
            $content = preg_replace(
6715
                "|(flashvars=\"file=)([^:/]+)/|",
6716
                "$1".api_get_path(
6717
                    REL_COURSE_PATH
6718
                ).$courseInfo['path'].'/document/',
6719
                $content
6720
            );
6721
            // The second regexp deals with audio/ urls.
6722
            $content = preg_replace(
6723
                "|(flashvars=\"file=)([^/]+)/|",
6724
                "$1".api_get_path(
6725
                    REL_COURSE_PATH
6726
                ).$courseInfo['path'].'/document/$2/',
6727
                $content
6728
            );
6729
            // For flv player: To prevent edition problem with firefox, we have to use a strange tip (don't blame me please).
6730
            $content = str_replace(
6731
                '</body>',
6732
                '<style type="text/css">body{}</style></body>',
6733
                $content
6734
            );
6735
        }
6736
6737
        if (!file_exists($filepath.$filename)) {
6738
            if ($fp = @ fopen($filepath.$filename, 'w')) {
6739
                fputs($fp, $content);
0 ignored issues
show
Bug introduced by
The call to fputs() has too few arguments starting with length. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6739
                /** @scrutinizer ignore-call */ 
6740
                fputs($fp, $content);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
6740
                fclose($fp);
6741
6742
                $file_size = filesize($filepath.$filename);
6743
                $save_file_path = $dir.$filename;
6744
6745
                $document_id = add_document(
6746
                    $courseInfo,
6747
                    $save_file_path,
6748
                    'file',
6749
                    $file_size,
6750
                    $tmp_filename,
6751
                    '',
6752
                    0, //readonly
6753
                    true,
6754
                    null,
6755
                    $sessionId,
6756
                    $creatorId
6757
                );
6758
6759
                if ($document_id) {
6760
                    api_item_property_update(
6761
                        $courseInfo,
6762
                        TOOL_DOCUMENT,
6763
                        $document_id,
6764
                        'DocumentAdded',
6765
                        $creatorId,
6766
                        null,
6767
                        null,
6768
                        null,
6769
                        null,
6770
                        $sessionId
6771
                    );
6772
6773
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6774
                    $new_title = $originalTitle;
6775
6776
                    if ($new_comment || $new_title) {
6777
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6778
                        $ct = '';
6779
                        if ($new_comment)
6780
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
6781
                        if ($new_title)
6782
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
6783
6784
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
6785
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
6786
                        Database::query($sql);
6787
                    }
6788
                }
6789
                return $document_id;
6790
            }
6791
        }
6792
    }
6793
6794
    /**
6795
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'
6796
     * @param 	array $_course array
6797
     * @return 	void
6798
     */
6799
    public function edit_document($_course)
6800
    {
6801
        $course_id = api_get_course_int_id();
6802
        $urlAppend = api_get_configuration_value('url_append');
6803
        // Please, do not modify this dirname formatting.
6804
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6805
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6806
6807
        if (strstr($dir, '..')) {
6808
            $dir = '/';
6809
        }
6810
6811
        if (isset($dir[0]) && $dir[0] == '.') {
6812
            $dir = substr($dir, 1);
6813
        }
6814
6815
        if (isset($dir[0]) && $dir[0] != '/') {
6816
            $dir = '/'.$dir;
6817
        }
6818
6819
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6820
            $dir .= '/';
6821
        }
6822
6823
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6824
6825
        if (!is_dir($filepath)) {
6826
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6827
        }
6828
6829
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6830
6831
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6832
            $document_id = intval($_POST['path']);
6833
            $sql = "SELECT path FROM ".$table_doc."
6834
                    WHERE c_id = $course_id AND id = ".$document_id;
6835
            $res = Database::query($sql);
6836
            $row = Database::fetch_array($res);
6837
            $content = stripslashes($_POST['content_lp']);
6838
            $file = $filepath.$row['path'];
6839
6840
            if ($fp = @fopen($file, 'w')) {
6841
                $content = str_replace(
6842
                    api_get_path(WEB_COURSE_PATH),
6843
                    $urlAppend.api_get_path(REL_COURSE_PATH),
0 ignored issues
show
Bug introduced by
Are you sure $urlAppend of type mixed|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6843
                    /** @scrutinizer ignore-type */ $urlAppend.api_get_path(REL_COURSE_PATH),
Loading history...
6844
                    $content
6845
                );
6846
                // Change the path of mp3 to absolute.
6847
                // The first regexp deals with :// urls.
6848
                $content = preg_replace("|(flashvars=\"file=)([^:/]+)/|", "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/', $content);
6849
                // The second regexp deals with audio/ urls.
6850
                $content = preg_replace("|(flashvars=\"file=)([^:/]+)/|", "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/', $content);
6851
                fputs($fp, $content);
0 ignored issues
show
Bug introduced by
The call to fputs() has too few arguments starting with length. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6851
                /** @scrutinizer ignore-call */ 
6852
                fputs($fp, $content);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
6852
                fclose($fp);
6853
6854
                $sql = "UPDATE ".$table_doc." SET
6855
                            title='".Database::escape_string($_POST['title'])."'
6856
                        WHERE c_id = ".$course_id." AND id = ".$document_id;
6857
6858
                Database::query($sql);
6859
            }
6860
        }
6861
    }
6862
6863
    /**
6864
     * Displays the selected item, with a panel for manipulating the item
6865
     * @param int $item_id
6866
     * @param string $msg
6867
     * @param bool $show_actions
6868
     * @return string
6869
     */
6870
    public function display_item($item_id, $msg = null, $show_actions = true)
6871
    {
6872
        $course_id = api_get_course_int_id();
6873
        $return = '';
6874
        if (is_numeric($item_id)) {
6875
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6876
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6877
                    WHERE lp.iid = ".intval($item_id);
6878
            $result = Database::query($sql);
6879
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6880
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6881
6882
                // Prevents wrong parent selection for document, see Bug#1251.
6883
                if ($row['item_type'] != 'dir') {
6884
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6885
                }
6886
6887
                if ($show_actions) {
6888
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6889
                }
6890
                $return .= '<div style="padding:10px;">';
6891
6892
                if ($msg != '') {
6893
                    $return .= $msg;
6894
                }
6895
6896
                $return .= '<h3>'.$row['title'].'</h3>';
6897
6898
                switch ($row['item_type']) {
6899
                    case TOOL_THREAD:
6900
                        $link = $this->rl_get_resource_link_for_learnpath(
6901
                            $course_id,
6902
                            $row['lp_id'],
6903
                            $item_id,
6904
                            0
6905
                        );
6906
                        $return .= Display::url(
6907
                            get_lang('GoToThread'),
6908
                            $link,
6909
                            ['class' => 'btn btn-primary']
6910
                        );
6911
                        break;
6912
                    case TOOL_FORUM:
6913
                        $return .= Display::url(
6914
                            get_lang('GoToForum'),
6915
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6916
                            ['class' => 'btn btn-primary']
6917
                        );
6918
                        break;
6919
                    case TOOL_QUIZ:
6920
                        if (!empty($row['path'])) {
6921
                            $exercise = new Exercise();
6922
                            $exercise->read($row['path']);
6923
                            $return .= $exercise->description.'<br />';
6924
                            $return .= Display::url(
6925
                                get_lang('GoToExercise'),
6926
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6927
                                ['class' => 'btn btn-primary']
6928
                            );
6929
                        }
6930
                        break;
6931
                    case TOOL_LP_FINAL_ITEM:
6932
                        $return .= $this->getSavedFinalItem();
6933
                        break;
6934
                    case TOOL_DOCUMENT:
6935
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6936
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
6937
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
6938
                        $result = Database::query($sql_doc);
6939
                        $path_file = Database::result($result, 0, 0);
6940
                        $path_parts = pathinfo($path_file);
6941
                        // TODO: Correct the following naive comparisons.
6942
                        if (in_array($path_parts['extension'], array(
6943
                            'html',
6944
                            'txt',
6945
                            'png',
6946
                            'jpg',
6947
                            'JPG',
6948
                            'jpeg',
6949
                            'JPEG',
6950
                            'gif',
6951
                            'swf',
6952
                            'pdf',
6953
                            'htm'
6954
                        ))) {
6955
                            $return .= $this->display_document($row['path'], true, true);
6956
                        }
6957
                        break;
6958
                    case TOOL_HOTPOTATOES:
6959
                        $return .= $this->display_document($row['path'], false, true);
6960
                        break;
6961
6962
                }
6963
                $return .= '</div>';
6964
            }
6965
        }
6966
6967
        return $return;
6968
    }
6969
6970
    /**
6971
     * Shows the needed forms for editing a specific item
6972
     * @param int $item_id
6973
     * @return string
6974
     */
6975
    public function display_edit_item($item_id)
6976
    {
6977
        $course_id = api_get_course_int_id();
6978
        $return = '';
6979
        if (is_numeric($item_id)) {
6980
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6981
            $sql = "SELECT * FROM $tbl_lp_item
6982
                    WHERE iid = ".intval($item_id);
6983
            $res = Database::query($sql);
6984
            $row = Database::fetch_array($res);
6985
            switch ($row['item_type']) {
6986
                case 'dir':
6987
                case 'asset':
6988
                case 'sco':
6989
                    if (isset($_GET['view']) && $_GET['view'] == 'build') {
6990
                        $return .= $this->display_manipulate($item_id, $row['item_type']);
6991
                        $return .= $this->display_item_form(
6992
                            $row['item_type'],
6993
                            get_lang('EditCurrentChapter').' :',
6994
                            'edit',
6995
                            $item_id,
6996
                            $row
6997
                        );
6998
                    } else {
6999
                        $return .= $this->display_item_small_form(
7000
                            $row['item_type'],
7001
                            get_lang('EditCurrentChapter').' :',
7002
                            $row
7003
                        );
7004
                    }
7005
                    break;
7006
                case TOOL_DOCUMENT:
7007
                    $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7008
                    $sql = "SELECT lp.*, doc.path as dir
7009
                            FROM $tbl_lp_item as lp
7010
                            LEFT JOIN $tbl_doc as doc
7011
                            ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7012
                            WHERE
7013
                                doc.c_id = $course_id AND
7014
                                lp.iid = ".intval($item_id);
7015
                    $res_step = Database::query($sql);
7016
                    $row_step = Database::fetch_array($res_step, 'ASSOC');
7017
                    $return .= $this->display_manipulate(
7018
                        $item_id,
7019
                        $row['item_type']
7020
                    );
7021
                    $return .= $this->display_document_form(
7022
                        'edit',
7023
                        $item_id,
7024
                        $row_step
7025
                    );
7026
                    break;
7027
                case TOOL_LINK:
7028
                    $link_id = (string) $row['path'];
7029
                    if (ctype_digit($link_id)) {
7030
                        $tbl_link = Database::get_course_table(TABLE_LINK);
7031
                        $sql_select = 'SELECT url FROM '.$tbl_link.'
7032
                                       WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7033
                        $res_link = Database::query($sql_select);
7034
                        $row_link = Database::fetch_array($res_link);
7035
                        if (is_array($row_link)) {
7036
                            $row['url'] = $row_link['url'];
7037
                        }
7038
                    }
7039
                    $return .= $this->display_manipulate(
7040
                        $item_id,
7041
                        $row['item_type']
7042
                    );
7043
                    $return .= $this->display_link_form('edit', $item_id, $row);
7044
                    break;
7045
                case TOOL_LP_FINAL_ITEM:
7046
                    Session::write('finalItem', true);
7047
                    $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7048
                    $sql = "SELECT lp.*, doc.path as dir
7049
                            FROM $tbl_lp_item as lp
7050
                            LEFT JOIN $tbl_doc as doc
7051
                            ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7052
                            WHERE
7053
                                doc.c_id = $course_id AND
7054
                                lp.iid = ".intval($item_id);
7055
                    $res_step = Database::query($sql);
7056
                    $row_step = Database::fetch_array($res_step, 'ASSOC');
7057
                    $return .= $this->display_manipulate(
7058
                        $item_id,
7059
                        $row['item_type']
7060
                    );
7061
                    $return .= $this->display_document_form(
7062
                        'edit',
7063
                        $item_id,
7064
                        $row_step
7065
                    );
7066
                    break;
7067
                case TOOL_QUIZ:
7068
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7069
                    $return .= $this->display_quiz_form('edit', $item_id, $row);
7070
                    break;
7071
                case TOOL_HOTPOTATOES:
7072
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7073
                    $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7074
                    break;
7075
                case TOOL_STUDENTPUBLICATION:
7076
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7077
                    $return .= $this->display_student_publication_form('edit', $item_id, $row);
7078
                    break;
7079
                case TOOL_FORUM:
7080
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7081
                    $return .= $this->display_forum_form('edit', $item_id, $row);
7082
                    break;
7083
                case TOOL_THREAD:
7084
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7085
                    $return .= $this->display_thread_form('edit', $item_id, $row);
7086
                    break;
7087
            }
7088
        }
7089
7090
        return $return;
7091
    }
7092
7093
    /**
7094
     * Function that displays a list with al the resources that
7095
     * could be added to the learning path
7096
     * @return bool
7097
     */
7098
    public function display_resources()
7099
    {
7100
        $course_code = api_get_course_id();
7101
7102
        // Get all the docs.
7103
        $documents = $this->get_documents(true);
7104
7105
        // Get all the exercises.
7106
        $exercises = $this->get_exercises();
7107
7108
        // Get all the links.
7109
        $links = $this->get_links();
7110
7111
        // Get all the student publications.
7112
        $works = $this->get_student_publications();
7113
7114
        // Get all the forums.
7115
        $forums = $this->get_forums(null, $course_code);
7116
7117
        // Get the final item form (see BT#11048) .
7118
        $finish = $this->getFinalItemForm();
7119
7120
        $headers = array(
7121
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7122
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7123
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7124
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7125
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7126
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7127
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7128
        );
7129
7130
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7131
        $dir = $_SESSION['oLP']->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7132
        echo Display::tabs(
7133
            $headers,
7134
            array(
7135
                $documents,
7136
                $exercises,
7137
                $links,
7138
                $works,
7139
                $forums,
7140
                $dir,
7141
                $finish,
7142
            ),
7143
            'resource_tab'
7144
        );
7145
7146
        return true;
7147
    }
7148
7149
    /**
7150
     * Returns the extension of a document
7151
     * @param string $filename
7152
     * @return string Extension (part after the last dot)
7153
     */
7154
    public function get_extension($filename)
7155
    {
7156
        $explode = explode('.', $filename);
7157
        return $explode[count($explode) - 1];
7158
    }
7159
7160
    /**
7161
     * Displays a document by id
7162
     *
7163
     * @param int $id
7164
     * @return string
7165
     */
7166
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7167
    {
7168
        $_course = api_get_course_info();
7169
        $course_id = api_get_course_int_id();
7170
        $return = '';
7171
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7172
        $sql_doc = "SELECT * FROM ".$tbl_doc."
7173
                    WHERE c_id = $course_id AND iid = $id";
7174
        $res_doc = Database::query($sql_doc);
7175
        $row_doc = Database::fetch_array($res_doc);
7176
7177
        // TODO: Add a path filter.
7178
        if ($iframe) {
7179
            $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="'.api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq().'"></iframe>';
7180
        } else {
7181
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7182
        }
7183
7184
        return $return;
7185
    }
7186
7187
    /**
7188
     * Return HTML form to add/edit a quiz
7189
     * @param	string	$action Action (add/edit)
7190
     * @param	integer	$id Item ID if already exists
7191
     * @param	mixed	$extra_info Extra information (quiz ID if integer)
7192
     * @return	string	HTML form
7193
     */
7194
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7195
    {
7196
        $course_id = api_get_course_int_id();
7197
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7198
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7199
7200
        if ($id != 0 && is_array($extra_info)) {
7201
            $item_title = $extra_info['title'];
7202
            $item_description = $extra_info['description'];
7203
        } elseif (is_numeric($extra_info)) {
7204
            $sql = "SELECT title, description
7205
                    FROM $tbl_quiz
7206
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7207
7208
            $result = Database::query($sql);
7209
            $row = Database::fetch_array($result);
7210
            $item_title = $row['title'];
7211
            $item_description = $row['description'];
7212
        } else {
7213
            $item_title = '';
7214
            $item_description = '';
7215
        }
7216
        $item_title = Security::remove_XSS($item_title);
7217
        $item_description = Security::remove_XSS($item_description);
7218
7219
        if ($id != 0 && is_array($extra_info)) {
7220
            $parent = $extra_info['parent_item_id'];
7221
        } else {
7222
            $parent = 0;
7223
        }
7224
7225
        $sql = "SELECT * FROM $tbl_lp_item 
7226
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7227
7228
        $result = Database::query($sql);
7229
        $arrLP = [];
7230
        while ($row = Database::fetch_array($result)) {
7231
            $arrLP[] = array(
7232
                'id' => $row['id'],
7233
                'item_type' => $row['item_type'],
7234
                'title' => $row['title'],
7235
                'path' => $row['path'],
7236
                'description' => $row['description'],
7237
                'parent_item_id' => $row['parent_item_id'],
7238
                'previous_item_id' => $row['previous_item_id'],
7239
                'next_item_id' => $row['next_item_id'],
7240
                'display_order' => $row['display_order'],
7241
                'max_score' => $row['max_score'],
7242
                'min_score' => $row['min_score'],
7243
                'mastery_score' => $row['mastery_score'],
7244
                'prerequisite' => $row['prerequisite'],
7245
                'max_time_allowed' => $row['max_time_allowed']
7246
            );
7247
        }
7248
7249
        $this->tree_array($arrLP);
7250
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
7251
        unset($this->arrMenu);
7252
7253
        $form = new FormValidator(
7254
            'quiz_form',
7255
            'POST',
7256
            $this->getCurrentBuildingModeURL()
7257
        );
7258
        $defaults = [];
7259
7260
        if ($action == 'add') {
7261
            $legend = get_lang('CreateTheExercise');
7262
        } elseif ($action == 'move') {
7263
            $legend = get_lang('MoveTheCurrentExercise');
7264
        } else {
7265
            $legend = get_lang('EditCurrentExecice');
7266
        }
7267
7268
        if (isset ($_GET['edit']) && $_GET['edit'] == 'true') {
7269
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7270
        }
7271
7272
        $form->addHeader($legend);
7273
7274
        if ($action != 'move') {
7275
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7276
            $defaults['title'] = $item_title;
7277
        }
7278
7279
        // Select for Parent item, root or chapter
7280
        $selectParent = $form->addSelect(
7281
            'parent',
7282
            get_lang('Parent'),
7283
            [],
7284
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7285
        );
7286
        $selectParent->addOption($this->name, 0);
7287
7288
        $arrHide = array(
7289
            $id
7290
        );
7291
        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...
7292
            if ($action != 'add') {
7293
                if (
7294
                    ($arrLP[$i]['item_type'] == 'dir') &&
7295
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7296
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7297
                ) {
7298
                    $selectParent->addOption(
7299
                        $arrLP[$i]['title'],
7300
                        $arrLP[$i]['id'],
7301
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7302
                    );
7303
7304
                    if ($parent == $arrLP[$i]['id']) {
7305
                        $selectParent->setSelected($arrLP[$i]['id']);
7306
                    }
7307
                } else {
7308
                    $arrHide[] = $arrLP[$i]['id'];
7309
                }
7310
            } else {
7311
                if ($arrLP[$i]['item_type'] == 'dir') {
7312
                    $selectParent->addOption(
7313
                        $arrLP[$i]['title'],
7314
                        $arrLP[$i]['id'], ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7315
                    );
7316
7317
                    if ($parent == $arrLP[$i]['id']) {
7318
                        $selectParent->setSelected($arrLP[$i]['id']);
7319
                    }
7320
                }
7321
            }
7322
        }
7323
        if (is_array($arrLP)) {
7324
            reset($arrLP);
7325
        }
7326
7327
        $selectPrevious = $form->addSelect(
7328
            'previous',
7329
            get_lang('Position'),
7330
            [],
7331
            ['id' => 'previous']
7332
        );
7333
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7334
7335
        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...
7336
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7337
                $arrLP[$i]['id'] != $id
7338
            ) {
7339
                $selectPrevious->addOption(
7340
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7341
                    $arrLP[$i]['id']
7342
                );
7343
7344
                if (is_array($extra_info)) {
7345
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7346
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7347
                    }
7348
                } elseif ($action == 'add') {
7349
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7350
                }
7351
            }
7352
        }
7353
7354
        if ($action != 'move') {
7355
            $id_prerequisite = 0;
7356
            if (is_array($arrLP)) {
7357
                foreach ($arrLP as $key => $value) {
7358
                    if ($value['id'] == $id) {
7359
                        $id_prerequisite = $value['prerequisite'];
7360
                        break;
7361
                    }
7362
                }
7363
            }
7364
            $arrHide = [];
7365
            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...
7366
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7367
                    if (is_array($extra_info)) {
7368
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7369
                            $s_selected_position = $arrLP[$i]['id'];
7370
                        }
7371
                    } elseif ($action == 'add') {
7372
                        $s_selected_position = 0;
7373
                    }
7374
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7375
                }
7376
            }
7377
        }
7378
7379
        if ($action == 'add') {
7380
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7381
        } else {
7382
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7383
        }
7384
7385
        if ($action == 'move') {
7386
            $form->addHidden('title', $item_title);
7387
            $form->addHidden('description', $item_description);
7388
        }
7389
7390
        if (is_numeric($extra_info)) {
7391
            $form->addHidden('path', $extra_info);
7392
        } elseif (is_array($extra_info)) {
7393
            $form->addHidden('path', $extra_info['path']);
7394
        }
7395
7396
        $form->addHidden('type', TOOL_QUIZ);
7397
        $form->addHidden('post_time', time());
7398
        $form->setDefaults($defaults);
7399
7400
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7401
    }
7402
7403
    /**
7404
     * Addition of Hotpotatoes tests
7405
     * @param	string	Action
7406
     * @param	integer	Internal ID of the item
7407
     * @param	mixed	Extra information - can be an array with title and description indexes
7408
     * @return  string	HTML structure to display the hotpotatoes addition formular
7409
     */
7410
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7411
    {
7412
        $course_id = api_get_course_int_id();
7413
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7414
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7415
7416
        if ($id != 0 && is_array($extra_info)) {
7417
            $item_title = stripslashes($extra_info['title']);
7418
            $item_description = stripslashes($extra_info['description']);
7419
        } elseif (is_numeric($extra_info)) {
7420
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7421
7422
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7423
                    WHERE
7424
                        c_id = ".$course_id." AND
7425
                        path LIKE '" . $uploadPath."/%/%htm%' AND
7426
                        iid = " . (int) $extra_info."
7427
                    ORDER BY iid ASC";
7428
7429
            $res_hot = Database::query($sql);
7430
            $row = Database::fetch_array($res_hot);
7431
7432
            $item_title = $row['title'];
7433
            $item_description = $row['description'];
7434
7435
            if (!empty ($row['comment'])) {
7436
                $item_title = $row['comment'];
7437
            }
7438
        } else {
7439
            $item_title = '';
7440
            $item_description = '';
7441
        }
7442
7443
        if ($id != 0 && is_array($extra_info)) {
7444
            $parent = $extra_info['parent_item_id'];
7445
        } else {
7446
            $parent = 0;
7447
        }
7448
7449
        $sql = "SELECT * FROM $tbl_lp_item
7450
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7451
        $result = Database::query($sql);
7452
        $arrLP = [];
7453
        while ($row = Database::fetch_array($result)) {
7454
            $arrLP[] = array(
7455
                'id' => $row['id'],
7456
                'item_type' => $row['item_type'],
7457
                'title' => $row['title'],
7458
                'path' => $row['path'],
7459
                'description' => $row['description'],
7460
                'parent_item_id' => $row['parent_item_id'],
7461
                'previous_item_id' => $row['previous_item_id'],
7462
                'next_item_id' => $row['next_item_id'],
7463
                'display_order' => $row['display_order'],
7464
                'max_score' => $row['max_score'],
7465
                'min_score' => $row['min_score'],
7466
                'mastery_score' => $row['mastery_score'],
7467
                'prerequisite' => $row['prerequisite'],
7468
                'max_time_allowed' => $row['max_time_allowed']
7469
            );
7470
        }
7471
7472
        $legend = '<legend>';
7473
        if ($action == 'add') {
7474
            $legend .= get_lang('CreateTheExercise');
7475
        } elseif ($action == 'move') {
7476
            $legend .= get_lang('MoveTheCurrentExercise');
7477
        } else {
7478
            $legend .= get_lang('EditCurrentExecice');
7479
        }
7480
        if (isset ($_GET['edit']) && $_GET['edit'] == 'true') {
7481
            $legend .= Display:: return_message(
7482
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7483
            );
7484
        }
7485
        $legend .= '</legend>';
7486
7487
        $return = '<form method="POST">';
7488
        $return .= $legend;
7489
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7490
        $return .= '<tr>';
7491
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7492
        $return .= '<td class="input">';
7493
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7494
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7495
        $arrHide = array(
7496
            $id
7497
        );
7498
7499
        if (count($arrLP) > 0) {
7500
            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...
7501
                if ($action != 'add') {
7502
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7503
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7504
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7505
                    ) {
7506
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7507
                    } else {
7508
                        $arrHide[] = $arrLP[$i]['id'];
7509
                    }
7510
                } else {
7511
                    if ($arrLP[$i]['item_type'] == 'dir')
7512
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7513
                }
7514
            }
7515
            reset($arrLP);
7516
        }
7517
7518
        $return .= '</select>';
7519
        $return .= '</td>';
7520
        $return .= '</tr>';
7521
        $return .= '<tr>';
7522
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7523
        $return .= '<td class="input">';
7524
        $return .= '<select id="previous" name="previous" size="1">';
7525
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7526
7527
        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...
7528
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7529
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7530
                    $selected = 'selected="selected" ';
7531
                } elseif ($action == 'add') {
7532
                    $selected = 'selected="selected" ';
7533
                } else {
7534
                    $selected = '';
7535
                }
7536
7537
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7538
            }
7539
        }
7540
7541
        $return .= '</select>';
7542
        $return .= '</td>';
7543
        $return .= '</tr>';
7544
7545
        if ($action != 'move') {
7546
            $return .= '<tr>';
7547
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7548
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7549
            $return .= '</tr>';
7550
            $id_prerequisite = 0;
7551
            if (is_array($arrLP) && count($arrLP) > 0) {
7552
                foreach ($arrLP as $key => $value) {
7553
                    if ($value['id'] == $id) {
7554
                        $id_prerequisite = $value['prerequisite'];
7555
                        break;
7556
                    }
7557
                }
7558
7559
                $arrHide = [];
7560
                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...
7561
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7562
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7563
                            $s_selected_position = $arrLP[$i]['id'];
7564
                        } elseif ($action == 'add') {
7565
                            $s_selected_position = 0;
7566
                        }
7567
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7568
                    }
7569
                }
7570
            }
7571
        }
7572
7573
        $return .= '<tr>';
7574
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.get_lang('SaveHotpotatoes').'</button></td>';
7575
        $return .= '</tr>';
7576
        $return .= '</table>';
7577
7578
        if ($action == 'move') {
7579
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7580
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7581
        }
7582
7583
        if (is_numeric($extra_info)) {
7584
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7585
        } elseif (is_array($extra_info)) {
7586
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7587
        }
7588
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7589
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7590
        $return .= '</form>';
7591
7592
        return $return;
7593
    }
7594
7595
    /**
7596
     * Return the form to display the forum edit/add option
7597
     * @param	string	Action (add/edit)
7598
     * @param	integer	ID of the lp_item if already exists
7599
     * @param	mixed	Forum ID or title
7600
     * @return	string	HTML form
7601
     */
7602
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7603
    {
7604
        $course_id = api_get_course_int_id();
7605
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7606
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7607
7608
        if ($id != 0 && is_array($extra_info)) {
7609
            $item_title = stripslashes($extra_info['title']);
7610
        } elseif (is_numeric($extra_info)) {
7611
            $sql = "SELECT forum_title as title, forum_comment as comment
7612
                    FROM " . $tbl_forum."
7613
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
7614
7615
            $result = Database::query($sql);
7616
            $row = Database::fetch_array($result);
7617
7618
            $item_title = $row['title'];
7619
            $item_description = $row['comment'];
7620
        } else {
7621
            $item_title = '';
7622
            $item_description = '';
7623
        }
7624
7625
        if ($id != 0 && is_array($extra_info)) {
7626
            $parent = $extra_info['parent_item_id'];
7627
        } else {
7628
            $parent = 0;
7629
        }
7630
7631
        $sql = "SELECT * FROM $tbl_lp_item
7632
                WHERE
7633
                    c_id = $course_id AND
7634
                    lp_id = " . $this->lp_id;
7635
        $result = Database::query($sql);
7636
        $arrLP = [];
7637
        while ($row = Database::fetch_array($result)) {
7638
            $arrLP[] = array(
7639
                'id' => $row['iid'],
7640
                'item_type' => $row['item_type'],
7641
                'title' => $row['title'],
7642
                'path' => $row['path'],
7643
                'description' => $row['description'],
7644
                'parent_item_id' => $row['parent_item_id'],
7645
                'previous_item_id' => $row['previous_item_id'],
7646
                'next_item_id' => $row['next_item_id'],
7647
                'display_order' => $row['display_order'],
7648
                'max_score' => $row['max_score'],
7649
                'min_score' => $row['min_score'],
7650
                'mastery_score' => $row['mastery_score'],
7651
                'prerequisite' => $row['prerequisite']
7652
            );
7653
        }
7654
7655
        $this->tree_array($arrLP);
7656
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
7657
        unset($this->arrMenu);
7658
7659
        if ($action == 'add') {
7660
            $legend = get_lang('CreateTheForum');
7661
        } elseif ($action == 'move') {
7662
            $legend = get_lang('MoveTheCurrentForum');
7663
        } else {
7664
            $legend = get_lang('EditCurrentForum');
7665
        }
7666
7667
        $form = new FormValidator(
7668
            'forum_form',
7669
            'POST',
7670
            $this->getCurrentBuildingModeURL()
7671
        );
7672
        $defaults = [];
7673
7674
        $form->addHeader($legend);
7675
7676
        if ($action != 'move') {
7677
            $form->addText(
7678
                'title',
7679
                get_lang('Title'),
7680
                true,
7681
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
7682
            );
7683
            $defaults['title'] = $item_title;
7684
        }
7685
7686
        $selectParent = $form->addSelect(
7687
            'parent',
7688
            get_lang('Parent'),
7689
            [],
7690
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7691
        );
7692
        $selectParent->addOption($this->name, 0);
7693
        $arrHide = array(
7694
            $id
7695
        );
7696
7697
        //$parent_item_id = $_SESSION['parent_item_id'];
7698
        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...
7699
            if ($action != 'add') {
7700
                if ($arrLP[$i]['item_type'] == 'dir' &&
7701
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7702
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7703
                ) {
7704
                    $selectParent->addOption(
7705
                        $arrLP[$i]['title'],
7706
                        $arrLP[$i]['id'],
7707
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7708
                    );
7709
7710
                    if ($parent == $arrLP[$i]['id']) {
7711
                        $selectParent->setSelected($arrLP[$i]['id']);
7712
                    }
7713
                } else {
7714
                    $arrHide[] = $arrLP[$i]['id'];
7715
                }
7716
            } else {
7717
                if ($arrLP[$i]['item_type'] == 'dir') {
7718
                    $selectParent->addOption(
7719
                        $arrLP[$i]['title'],
7720
                        $arrLP[$i]['id'],
7721
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7722
                    );
7723
7724
                    if ($parent == $arrLP[$i]['id']) {
7725
                        $selectParent->setSelected($arrLP[$i]['id']);
7726
                    }
7727
                }
7728
            }
7729
        }
7730
7731
        if (is_array($arrLP)) {
7732
            reset($arrLP);
7733
        }
7734
7735
        $selectPrevious = $form->addSelect(
7736
            'previous',
7737
            get_lang('Position'),
7738
            [],
7739
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7740
        );
7741
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7742
7743
        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...
7744
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7745
                $arrLP[$i]['id'] != $id
7746
            ) {
7747
                $selectPrevious->addOption(
7748
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7749
                    $arrLP[$i]['id']
7750
                );
7751
7752
                if (isset($extra_info['previous_item_id']) &&
7753
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7754
                ) {
7755
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7756
                } elseif ($action == 'add') {
7757
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7758
                }
7759
            }
7760
        }
7761
7762
        if ($action != 'move') {
7763
            $id_prerequisite = 0;
7764
            if (is_array($arrLP)) {
7765
                foreach ($arrLP as $key => $value) {
7766
                    if ($value['id'] == $id) {
7767
                        $id_prerequisite = $value['prerequisite'];
7768
                        break;
7769
                    }
7770
                }
7771
            }
7772
7773
            $arrHide = [];
7774
            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...
7775
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7776
                    if (isset($extra_info['previous_item_id']) &&
7777
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7778
                    ) {
7779
                        $s_selected_position = $arrLP[$i]['id'];
7780
                    } elseif ($action == 'add') {
7781
                        $s_selected_position = 0;
7782
                    }
7783
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7784
                }
7785
            }
7786
        }
7787
7788
        if ($action == 'add') {
7789
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
7790
        } else {
7791
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
7792
        }
7793
7794
        if ($action == 'move') {
7795
            $form->addHidden('title', $item_title);
7796
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
7797
        }
7798
7799
        if (is_numeric($extra_info)) {
7800
            $form->addHidden('path', $extra_info);
7801
        } elseif (is_array($extra_info)) {
7802
            $form->addHidden('path', $extra_info['path']);
7803
        }
7804
        $form->addHidden('type', TOOL_FORUM);
7805
        $form->addHidden('post_time', time());
7806
        $form->setDefaults($defaults);
7807
7808
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7809
    }
7810
7811
    /**
7812
     * Return HTML form to add/edit forum threads
7813
     * @param	string	Action (add/edit)
7814
     * @param	integer	Item ID if already exists in learning path
7815
     * @param	mixed	Extra information (thread ID if integer)
7816
     * @return 	string	HTML form
7817
     */
7818
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7819
    {
7820
        $course_id = api_get_course_int_id();
7821
        if (empty($course_id)) {
7822
            return null;
7823
        }
7824
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7825
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7826
7827
        if ($id != 0 && is_array($extra_info)) {
7828
            $item_title = stripslashes($extra_info['title']);
7829
        } elseif (is_numeric($extra_info)) {
7830
            $sql = "SELECT thread_title as title FROM $tbl_forum
7831
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7832
7833
            $result = Database::query($sql);
7834
            $row = Database::fetch_array($result);
7835
7836
            $item_title = $row['title'];
7837
            $item_description = '';
7838
        } else {
7839
            $item_title = '';
7840
            $item_description = '';
7841
        }
7842
7843
        if ($id != 0 && is_array($extra_info)) {
7844
            $parent = $extra_info['parent_item_id'];
7845
        } else {
7846
            $parent = 0;
7847
        }
7848
7849
        $sql = "SELECT * FROM $tbl_lp_item
7850
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7851
        $result = Database::query($sql);
7852
7853
        $arrLP = [];
7854
        while ($row = Database::fetch_array($result)) {
7855
            $arrLP[] = array(
7856
                'id' => $row['iid'],
7857
                'item_type' => $row['item_type'],
7858
                'title' => $row['title'],
7859
                'path' => $row['path'],
7860
                'description' => $row['description'],
7861
                'parent_item_id' => $row['parent_item_id'],
7862
                'previous_item_id' => $row['previous_item_id'],
7863
                'next_item_id' => $row['next_item_id'],
7864
                'display_order' => $row['display_order'],
7865
                'max_score' => $row['max_score'],
7866
                'min_score' => $row['min_score'],
7867
                'mastery_score' => $row['mastery_score'],
7868
                'prerequisite' => $row['prerequisite']
7869
            );
7870
        }
7871
7872
        $this->tree_array($arrLP);
7873
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
7874
        unset($this->arrMenu);
7875
7876
        $form = new FormValidator(
7877
            'thread_form',
7878
            'POST',
7879
            $this->getCurrentBuildingModeURL()
7880
        );
7881
        $defaults = [];
7882
7883
        if ($action == 'add') {
7884
            $legend = get_lang('CreateTheForum');
7885
        } elseif ($action == 'move') {
7886
            $legend = get_lang('MoveTheCurrentForum');
7887
        } else {
7888
            $legend = get_lang('EditCurrentForum');
7889
        }
7890
7891
        $form->addHeader($legend);
7892
        $selectParent = $form->addSelect(
7893
            'parent',
7894
            get_lang('Parent'),
7895
            [],
7896
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7897
        );
7898
        $selectParent->addOption($this->name, 0);
7899
7900
        $arrHide = array(
7901
            $id
7902
        );
7903
7904
        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...
7905
            if ($action != 'add') {
7906
                if (
7907
                    ($arrLP[$i]['item_type'] == 'dir') &&
7908
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7909
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7910
                ) {
7911
                    $selectParent->addOption(
7912
                        $arrLP[$i]['title'],
7913
                        $arrLP[$i]['id'],
7914
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7915
                    );
7916
7917
                    if ($parent == $arrLP[$i]['id']) {
7918
                        $selectParent->setSelected($arrLP[$i]['id']);
7919
                    }
7920
                } else {
7921
                    $arrHide[] = $arrLP[$i]['id'];
7922
                }
7923
            } else {
7924
                if ($arrLP[$i]['item_type'] == 'dir') {
7925
                    $selectParent->addOption(
7926
                        $arrLP[$i]['title'],
7927
                        $arrLP[$i]['id'],
7928
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7929
                    );
7930
7931
                    if ($parent == $arrLP[$i]['id']) {
7932
                        $selectParent->setSelected($arrLP[$i]['id']);
7933
                    }
7934
                }
7935
            }
7936
        }
7937
7938
        if ($arrLP != null) {
7939
            reset($arrLP);
7940
        }
7941
7942
        $selectPrevious = $form->addSelect(
7943
            'previous',
7944
            get_lang('Position'),
7945
            [],
7946
            ['id' => 'previous']
7947
        );
7948
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7949
7950
        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...
7951
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7952
                $selectPrevious->addOption(
7953
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7954
                    $arrLP[$i]['id']
7955
                );
7956
7957
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7958
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7959
                } elseif ($action == 'add') {
7960
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7961
                }
7962
            }
7963
        }
7964
7965
        if ($action != 'move') {
7966
            $form->addText(
7967
                'title',
7968
                get_lang('Title'),
7969
                true,
7970
                ['id' => 'idTitle']
7971
            );
7972
            $defaults['title'] = $item_title;
7973
7974
            $id_prerequisite = 0;
7975
            if ($arrLP != null) {
7976
                foreach ($arrLP as $key => $value) {
7977
                    if ($value['id'] == $id) {
7978
                        $id_prerequisite = $value['prerequisite'];
7979
                        break;
7980
                    }
7981
                }
7982
            }
7983
7984
            $arrHide = [];
7985
            $s_selected_position = 0;
7986
            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...
7987
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7988
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id'])
7989
                        $s_selected_position = $arrLP[$i]['id'];
7990
                    elseif ($action == 'add') $s_selected_position = 0;
7991
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7992
7993
                }
7994
            }
7995
7996
            $selectPrerequisites = $form->addSelect(
7997
                'prerequisites',
7998
                get_lang('LearnpathPrerequisites'),
7999
                [],
8000
                ['id' => 'prerequisites']
8001
            );
8002
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8003
8004
            foreach ($arrHide as $key => $value) {
8005
                $selectPrerequisites->addOption($value['value'], $key);
8006
8007
                if ($key == $s_selected_position && $action == 'add') {
8008
                    $selectPrerequisites->setSelected($key);
8009
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8010
                    $selectPrerequisites->setSelected($key);
8011
                }
8012
            }
8013
        }
8014
8015
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8016
8017
        if ($action == 'move') {
8018
            $form->addHidden('title', $item_title);
8019
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
8020
        }
8021
8022
        if (is_numeric($extra_info)) {
8023
            $form->addHidden('path', $extra_info);
8024
        }
8025
        elseif (is_array($extra_info)) {
8026
            $form->addHidden('path', $extra_info['path']);
8027
        }
8028
8029
        $form->addHidden('type', TOOL_THREAD);
8030
        $form->addHidden('post_time', time());
8031
        $form->setDefaults($defaults);
8032
8033
        return $form->returnForm();
8034
    }
8035
8036
    /**
8037
     * Return the HTML form to display an item (generally a dir item)
8038
     * @param	string	Item type (dir)
8039
     * @param	string	Title (optional, only when creating)
8040
     * @param	string	Action ('add'/'edit')
8041
     * @param	integer	lp_item ID
8042
     * @param	mixed	Extra info
8043
     * @return	string 	HTML form
8044
     */
8045
    public function display_item_form(
8046
        $item_type,
8047
        $title = '',
8048
        $action = 'add_item',
8049
        $id = 0,
8050
        $extra_info = 'new'
8051
    ) {
8052
        $course_id = api_get_course_int_id();
8053
        $_course = api_get_course_info();
8054
8055
        global $charset;
8056
8057
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8058
8059
        if ($id != 0 && is_array($extra_info)) {
8060
            $item_title = $extra_info['title'];
8061
            $item_description = $extra_info['description'];
8062
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8063
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8064
        } else {
8065
            $item_title = '';
8066
            $item_description = '';
8067
            $item_path_fck = '';
8068
        }
8069
8070
        if ($id != 0 && is_array($extra_info)) {
8071
            $parent = $extra_info['parent_item_id'];
8072
        } else {
8073
            $parent = 0;
8074
        }
8075
8076
        $id  = intval($id);
8077
        $sql = "SELECT * FROM $tbl_lp_item
8078
                WHERE
8079
                    lp_id = " . $this->lp_id." AND
8080
                    iid != $id";
8081
8082
        if ($item_type == 'dir') {
8083
            $sql .= " AND parent_item_id = 0";
8084
        }
8085
8086
        $result = Database::query($sql);
8087
        $arrLP = [];
8088
8089
        while ($row = Database::fetch_array($result)) {
8090
            $arrLP[] = array(
8091
                'id' => $row['iid'],
8092
                'item_type' => $row['item_type'],
8093
                'title' => $row['title'],
8094
                'path' => $row['path'],
8095
                'description' => $row['description'],
8096
                'parent_item_id' => $row['parent_item_id'],
8097
                'previous_item_id' => $row['previous_item_id'],
8098
                'next_item_id' => $row['next_item_id'],
8099
                'max_score' => $row['max_score'],
8100
                'min_score' => $row['min_score'],
8101
                'mastery_score' => $row['mastery_score'],
8102
                'prerequisite' => $row['prerequisite'],
8103
                'display_order' => $row['display_order']
8104
            );
8105
        }
8106
8107
        $this->tree_array($arrLP);
8108
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
8109
        unset($this->arrMenu);
8110
8111
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8112
8113
        $form = new FormValidator('form', 'POST', $url);
8114
        $defaults['title'] = api_html_entity_decode(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$defaults was never initialized. Although not strictly required by PHP, it is generally a good practice to add $defaults = array(); before regardless.
Loading history...
8115
            $item_title,
8116
            ENT_QUOTES,
8117
            $charset
8118
        );
8119
        $defaults['description'] = $item_description;
8120
8121
        $form->addElement('header', $title);
8122
8123
        //$arrHide = array($id);
8124
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arrHide was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arrHide = array(); before regardless.
Loading history...
8125
        $arrHide[0]['padding'] = 20;
8126
        $charset = api_get_system_encoding();
8127
8128
        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...
8129
            if ($action != 'add') {
8130
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8131
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8132
                ) {
8133
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8134
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8135
                    if ($parent == $arrLP[$i]['id']) {
8136
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8137
                    }
8138
                }
8139
            } else {
8140
                if ($arrLP[$i]['item_type'] == 'dir') {
8141
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8142
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8143
                    if ($parent == $arrLP[$i]['id']) {
8144
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8145
                    }
8146
                }
8147
            }
8148
        }
8149
8150
        if ($action != 'move') {
8151
            $form->addElement('text', 'title', get_lang('Title'));
8152
            $form->applyFilter('title', 'html_filter');
8153
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8154
        } else {
8155
            $form->addElement('hidden', 'title');
8156
        }
8157
8158
        $parent_select = $form->addElement(
8159
            'select',
8160
            'parent',
8161
            get_lang('Parent'),
8162
            '',
8163
            array(
8164
                'id' => 'idParent',
8165
                'onchange' => "javascript: load_cbo(this.value);",
8166
            )
8167
        );
8168
8169
        foreach ($arrHide as $key => $value) {
8170
            $parent_select->addOption(
0 ignored issues
show
Bug introduced by
The method addOption() does not exist on HTML_QuickForm_element. It seems like you code against a sub-type of HTML_QuickForm_element such as HTML_QuickForm_select. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

8170
            $parent_select->/** @scrutinizer ignore-call */ 
8171
                            addOption(
Loading history...
8171
                $value['value'],
8172
                $key,
8173
                'style="padding-left:'.$value['padding'].'px;"'
8174
            );
8175
        }
8176
        if (!empty($s_selected_parent)) {
8177
            $parent_select->setSelected($s_selected_parent);
0 ignored issues
show
Bug introduced by
The method setSelected() does not exist on HTML_QuickForm_element. It seems like you code against a sub-type of HTML_QuickForm_element such as HTML_QuickForm_select. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

8177
            $parent_select->/** @scrutinizer ignore-call */ 
8178
                            setSelected($s_selected_parent);
Loading history...
8178
        }
8179
8180
        if (is_array($arrLP)) {
8181
            reset($arrLP);
8182
        }
8183
        $arrHide = [];
8184
        // POSITION
8185
        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...
8186
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8187
                //this is the same!
8188
                if (isset($extra_info['previous_item_id']) &&
8189
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8190
                ) {
8191
                    $s_selected_position = $arrLP[$i]['id'];
8192
                } elseif ($action == 'add') {
8193
                    $s_selected_position = $arrLP[$i]['id'];
8194
                }
8195
8196
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8197
            }
8198
        }
8199
8200
        $position = $form->addElement(
8201
            'select',
8202
            'previous',
8203
            get_lang('Position'),
8204
            '',
8205
            array('id' => 'previous')
8206
        );
8207
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8208
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8209
8210
        foreach ($arrHide as $key => $value) {
8211
            $position->addOption($value['value'].'"', $key, 'style="padding-left:'.$padding.'px;"');
8212
        }
8213
8214
        if (!empty ($s_selected_position)) {
8215
            $position->setSelected($s_selected_position);
8216
        }
8217
8218
        if (is_array($arrLP)) {
8219
            reset($arrLP);
8220
        }
8221
8222
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8223
8224
        //fix in order to use the tab
8225
        if ($item_type == 'dir') {
8226
            $form->addElement('hidden', 'type', 'dir');
8227
        }
8228
8229
        $extension = null;
8230
        if (!empty($item_path)) {
8231
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8232
        }
8233
8234
        //assets can't be modified
8235
        //$item_type == 'asset' ||
8236
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8237
            if ($item_type == 'sco') {
8238
                $form->addElement(
8239
                    'html',
8240
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8241
                );
8242
            }
8243
            $renderer = $form->defaultRenderer();
8244
            $renderer->setElementTemplate('<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}', 'content_lp');
8245
8246
            $relative_prefix = '';
8247
8248
            $editor_config = array(
8249
                'ToolbarSet' => 'LearningPathDocuments',
8250
                'Width' => '100%',
8251
                'Height' => '500',
8252
                'FullPage' => true,
8253
                'CreateDocumentDir' => $relative_prefix,
8254
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8255
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck
8256
            );
8257
8258
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8259
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8260
            $defaults['content_lp'] = file_get_contents($content_path);
8261
        }
8262
8263
        $form->addElement('hidden', 'type', $item_type);
8264
        $form->addElement('hidden', 'post_time', time());
8265
        $form->setDefaults($defaults);
8266
        return $form->returnForm();
8267
    }
8268
8269
    /**
8270
     * @return string
8271
     */
8272
    public function getCurrentBuildingModeURL()
8273
    {
8274
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8275
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8276
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8277
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8278
8279
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8280
8281
        return $currentUrl;
8282
    }
8283
8284
    /**
8285
     * Returns the form to update or create a document
8286
     * @param	string	$action (add/edit)
8287
     * @param	integer	$id ID of the lp_item (if already exists)
8288
     * @param	mixed	$extra_info Integer if document ID, string if info ('new')
8289
     * @return	string	HTML form
8290
     */
8291
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8292
    {
8293
        $course_id = api_get_course_int_id();
8294
        $_course = api_get_course_info();
8295
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8296
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8297
8298
        $no_display_edit_textarea = false;
8299
        $item_description = '';
8300
        //If action==edit document
8301
        //We don't display the document form if it's not an editable document (html or txt file)
8302
        if ($action == 'edit') {
8303
            if (is_array($extra_info)) {
8304
                $path_parts = pathinfo($extra_info['dir']);
8305
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8306
                    $no_display_edit_textarea = true;
8307
                }
8308
            }
8309
        }
8310
        $no_display_add = false;
8311
8312
        // If action==add an existing document
8313
        // We don't display the document form if it's not an editable document (html or txt file).
8314
        if ($action == 'add') {
8315
            if (is_numeric($extra_info)) {
8316
                $sql_doc = "SELECT path FROM $tbl_doc 
8317
                            WHERE c_id = $course_id AND iid = ".intval($extra_info);
8318
                $result = Database::query($sql_doc);
8319
                $path_file = Database::result($result, 0, 0);
8320
                $path_parts = pathinfo($path_file);
8321
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8322
                    $no_display_add = true;
8323
                }
8324
            }
8325
        }
8326
        if ($id != 0 && is_array($extra_info)) {
8327
            $item_title = stripslashes($extra_info['title']);
8328
            $item_description = stripslashes($extra_info['description']);
8329
            $item_terms = stripslashes($extra_info['terms']);
8330
            if (empty ($item_title)) {
8331
                $path_parts = pathinfo($extra_info['path']);
8332
                $item_title = stripslashes($path_parts['filename']);
8333
            }
8334
        } elseif (is_numeric($extra_info)) {
8335
            $sql = "SELECT path, title FROM $tbl_doc
8336
                    WHERE
8337
                        c_id = ".$course_id." AND
8338
                        iid = " . intval($extra_info);
8339
            $result = Database::query($sql);
8340
            $row = Database::fetch_array($result);
8341
            $item_title = $row['title'];
8342
            $item_title = str_replace('_', ' ', $item_title);
8343
            if (empty ($item_title)) {
8344
                $path_parts = pathinfo($row['path']);
8345
                $item_title = stripslashes($path_parts['filename']);
8346
            }
8347
        } else {
8348
            $item_title = '';
8349
            $item_description = '';
8350
        }
8351
        $return = '<legend>';
8352
8353
        if ($id != 0 && is_array($extra_info)) {
8354
            $parent = $extra_info['parent_item_id'];
8355
        } else {
8356
            $parent = 0;
8357
        }
8358
8359
        $sql = "SELECT * FROM $tbl_lp_item
8360
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8361
        $result = Database::query($sql);
8362
        $arrLP = [];
8363
8364
        while ($row = Database::fetch_array($result)) {
8365
            $arrLP[] = array(
8366
                'id' => $row['id'],
8367
                'item_type' => $row['item_type'],
8368
                'title' => $row['title'],
8369
                'path' => $row['path'],
8370
                'description' => $row['description'],
8371
                'parent_item_id' => $row['parent_item_id'],
8372
                'previous_item_id' => $row['previous_item_id'],
8373
                'next_item_id' => $row['next_item_id'],
8374
                'display_order' => $row['display_order'],
8375
                'max_score' => $row['max_score'],
8376
                'min_score' => $row['min_score'],
8377
                'mastery_score' => $row['mastery_score'],
8378
                'prerequisite' => $row['prerequisite']
8379
            );
8380
        }
8381
8382
        $this->tree_array($arrLP);
8383
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
8384
        unset($this->arrMenu);
8385
8386
        if ($action == 'add') {
8387
            $return .= get_lang('CreateTheDocument');
8388
        } elseif ($action == 'move') {
8389
            $return .= get_lang('MoveTheCurrentDocument');
8390
        } else {
8391
            $return .= get_lang('EditTheCurrentDocument');
8392
        }
8393
        $return .= '</legend>';
8394
8395
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8396
            $return .= Display::return_message(
8397
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8398
                false
8399
            );
8400
        }
8401
        $form = new FormValidator(
8402
            'form',
8403
            'POST',
8404
            $this->getCurrentBuildingModeURL(),
8405
            '',
8406
            array('enctype' => 'multipart/form-data')
8407
        );
8408
        $defaults['title'] = Security::remove_XSS($item_title);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$defaults was never initialized. Although not strictly required by PHP, it is generally a good practice to add $defaults = array(); before regardless.
Loading history...
8409
        if (empty($item_title)) {
8410
            $defaults['title'] = Security::remove_XSS($item_title);
8411
        }
8412
        $defaults['description'] = $item_description;
8413
        $form->addElement('html', $return);
8414
8415
        if ($action != 'move') {
8416
            $data = $this->generate_lp_folder($_course);
8417
            if ($action != 'edit') {
8418
                $folders = DocumentManager::get_all_document_folders(
8419
                    $_course,
8420
                    0,
8421
                    true
8422
                );
8423
                DocumentManager::build_directory_selector(
8424
                    $folders,
8425
                    '',
8426
                    [],
8427
                    true,
8428
                    $form,
8429
                    'directory_parent_id'
8430
                );
8431
            }
8432
8433
            if (isset($data['id'])) {
8434
                $defaults['directory_parent_id'] = $data['id'];
8435
            }
8436
8437
            $form->addElement(
8438
                'text',
8439
                'title',
8440
                get_lang('Title'),
8441
                array('id' => 'idTitle', 'class' => 'col-md-4')
8442
            );
8443
            $form->applyFilter('title', 'html_filter');
8444
        }
8445
8446
        $arrHide[0]['value'] = $this->name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arrHide was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arrHide = array(); before regardless.
Loading history...
8447
        $arrHide[0]['padding'] = 20;
8448
8449
        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...
8450
            if ($action != 'add') {
8451
                if ($arrLP[$i]['item_type'] == 'dir' &&
8452
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8453
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8454
                ) {
8455
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8456
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8457
                    if ($parent == $arrLP[$i]['id']) {
8458
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8459
                    }
8460
                }
8461
            } else {
8462
                if ($arrLP[$i]['item_type'] == 'dir') {
8463
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8464
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8465
                    if ($parent == $arrLP[$i]['id']) {
8466
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8467
                    }
8468
                }
8469
            }
8470
        }
8471
8472
        $parent_select = $form->addSelect(
8473
            'parent',
8474
            get_lang('Parent'),
8475
            [],
8476
            [
8477
                'id' => 'idParent',
8478
                'onchange' => 'javascript: load_cbo(this.value);',
8479
            ]
8480
        );
8481
8482
        $my_count = 0;
8483
        foreach ($arrHide as $key => $value) {
8484
            if ($my_count != 0) {
8485
                // The LP name is also the first section and is not in the same charset like the other sections.
8486
                $value['value'] = Security::remove_XSS($value['value']);
8487
                $parent_select->addOption(
8488
                    $value['value'],
8489
                    $key,
8490
                    'style="padding-left:'.$value['padding'].'px;"'
8491
                );
8492
            } else {
8493
                $value['value'] = Security::remove_XSS($value['value']);
8494
                $parent_select->addOption(
8495
                    $value['value'],
8496
                    $key,
8497
                    'style="padding-left:'.$value['padding'].'px;"'
8498
                );
8499
            }
8500
            $my_count++;
8501
        }
8502
8503
        if (!empty($id)) {
8504
            $parent_select->setSelected($parent);
8505
        } else {
8506
            $parent_item_id = Session::read('parent_item_id', 0);
8507
            $parent_select->setSelected($parent_item_id);
8508
        }
8509
8510
        if (is_array($arrLP)) {
8511
            reset($arrLP);
8512
        }
8513
8514
        $arrHide = [];
8515
        $s_selected_position = null;
8516
8517
        // POSITION
8518
        $lastPosition = null;
8519
8520
        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...
8521
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8522
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8523
            ) {
8524
                if ((isset($extra_info['previous_item_id']) &&
8525
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8526
                ) {
8527
                    $s_selected_position = $arrLP[$i]['id'];
8528
                }
8529
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8530
            }
8531
            $lastPosition = $arrLP[$i]['id'];
8532
        }
8533
8534
        if (empty($s_selected_position)) {
8535
            $s_selected_position = $lastPosition;
8536
        }
8537
8538
        $position = $form->addSelect(
8539
            'previous',
8540
            get_lang('Position'),
8541
            [],
8542
            ['id' => 'previous']
8543
        );
8544
        $position->addOption(get_lang('FirstPosition'), 0);
8545
8546
        foreach ($arrHide as $key => $value) {
8547
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8548
            $position->addOption(
8549
                $value['value'],
8550
                $key,
8551
                'style="padding-left:'.$padding.'px;"'
8552
            );
8553
        }
8554
        $position->setSelected($s_selected_position);
8555
8556
        if (is_array($arrLP)) {
8557
            reset($arrLP);
8558
        }
8559
8560
        if ($action != 'move') {
8561
            $id_prerequisite = 0;
8562
            if (is_array($arrLP)) {
8563
                foreach ($arrLP as $key => $value) {
8564
                    if ($value['id'] == $id) {
8565
                        $id_prerequisite = $value['prerequisite'];
8566
                        break;
8567
                    }
8568
                }
8569
            }
8570
8571
            $arrHide = [];
8572
            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...
8573
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8574
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8575
                ) {
8576
                    if (isset($extra_info['previous_item_id']) &&
8577
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8578
                    ) {
8579
                        $s_selected_position = $arrLP[$i]['id'];
8580
                    } elseif ($action == 'add') {
8581
                        $s_selected_position = $arrLP[$i]['id'];
8582
                    }
8583
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8584
                }
8585
            }
8586
8587
            if (!$no_display_add) {
8588
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8589
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8590
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8591
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8592
                ) {
8593
                    if (isset ($_POST['content'])) {
8594
                        $content = stripslashes($_POST['content']);
8595
                    } elseif (is_array($extra_info)) {
8596
                        //If it's an html document or a text file
8597
                        if (!$no_display_edit_textarea) {
8598
                            $content = $this->display_document(
8599
                                $extra_info['path'],
8600
                                false,
8601
                                false
8602
                            );
8603
                        }
8604
                    } elseif (is_numeric($extra_info)) {
8605
                        $content = $this->display_document(
8606
                            $extra_info,
8607
                            false,
8608
                            false
8609
                        );
8610
                    } else {
8611
                        $content = '';
8612
                    }
8613
8614
                    if (!$no_display_edit_textarea) {
8615
                        // We need to calculate here some specific settings for the online editor.
8616
                        // The calculated settings work for documents in the Documents tool
8617
                        // (on the root or in subfolders).
8618
                        // For documents in native scorm packages it is unclear whether the
8619
                        // online editor should be activated or not.
8620
8621
                        // A new document, it is in the root of the repository.
8622
                        $relative_path 	 = '';
8623
                        $relative_prefix = '';
8624
                        if (is_array($extra_info) && $extra_info != 'new') {
8625
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8626
                            $relative_path = explode('/', $extra_info['dir']);
8627
                            $cnt = count($relative_path) - 2;
8628
                            if ($cnt < 0) {
8629
                                $cnt = 0;
8630
                            }
8631
                            $relative_prefix = str_repeat('../', $cnt);
8632
                            $relative_path 	 = array_slice($relative_path, 1, $cnt);
8633
                            $relative_path 	 = implode('/', $relative_path);
8634
                            if (strlen($relative_path) > 0) {
8635
                                $relative_path = $relative_path.'/';
8636
                            }
8637
                        } else {
8638
                            $result = $this->generate_lp_folder($_course);
8639
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8640
                            $relative_prefix = '../../';
8641
                        }
8642
8643
                        $editor_config = array(
8644
                            'ToolbarSet' => 'LearningPathDocuments',
8645
                            'Width' => '100%',
8646
                            'Height' => '500',
8647
                            'FullPage' => true,
8648
                            'CreateDocumentDir' => $relative_prefix,
8649
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8650
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path
8651
                        );
8652
8653
                        if ($_GET['action'] == 'add_item') {
8654
                            $class = 'add';
8655
                            $text = get_lang('LPCreateDocument');
8656
                        } else {
8657
                            if ($_GET['action'] == 'edit_item') {
8658
                                $class = 'save';
8659
                                $text = get_lang('SaveDocument');
8660
                            }
8661
                        }
8662
8663
                        $form->addButtonSave($text, 'submit_button');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $text does not seem to be defined for all execution paths leading up to this point.
Loading history...
8664
                        $renderer = $form->defaultRenderer();
8665
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8666
                        $form->addElement('html', '<div class="editor-lp">');
8667
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8668
                        $form->addElement('html', '</div>');
8669
                        $defaults['content_lp'] = $content;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $content does not seem to be defined for all execution paths leading up to this point.
Loading history...
8670
                    }
8671
                } elseif (is_numeric($extra_info)) {
8672
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8673
8674
                    $return = $this->display_document($extra_info, true, true, true);
8675
                    $form->addElement('html', $return);
8676
                }
8677
            }
8678
        }
8679
        if (isset($extra_info['item_type']) &&
8680
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8681
        ) {
8682
            $parent_select->freeze();
8683
            $position->freeze();
8684
        }
8685
8686
        if ($action == 'move') {
8687
            $form->addElement('hidden', 'title', $item_title);
8688
            $form->addElement('hidden', 'description', $item_description);
8689
        }
8690
        if (is_numeric($extra_info)) {
8691
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8692
            $form->addElement('hidden', 'path', $extra_info);
8693
        } elseif (is_array($extra_info)) {
8694
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8695
            $form->addElement('hidden', 'path', $extra_info['path']);
8696
        }
8697
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8698
        $form->addElement('hidden', 'post_time', time());
8699
        $form->setDefaults($defaults);
8700
8701
        return $form->returnForm();
8702
    }
8703
8704
    /**
8705
     * Return HTML form to add/edit a link item
8706
     * @param string	$action (add/edit)
8707
     * @param integer	$id Item ID if exists
8708
     * @param mixed		$extra_info
8709
     * @return	string	HTML form
8710
     */
8711
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
8712
    {
8713
        $course_id = api_get_course_int_id();
8714
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8715
        $tbl_link = Database::get_course_table(TABLE_LINK);
8716
8717
        if ($id != 0 && is_array($extra_info)) {
8718
            $item_title = stripslashes($extra_info['title']);
8719
            $item_description = stripslashes($extra_info['description']);
8720
            $item_url = stripslashes($extra_info['url']);
8721
        } elseif (is_numeric($extra_info)) {
8722
            $extra_info = intval($extra_info);
8723
            $sql = "SELECT title, description, url FROM ".$tbl_link."
8724
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
8725
            $result = Database::query($sql);
8726
            $row = Database::fetch_array($result);
8727
            $item_title       = $row['title'];
8728
            $item_description = $row['description'];
8729
            $item_url = $row['url'];
8730
        } else {
8731
            $item_title = '';
8732
            $item_description = '';
8733
            $item_url = '';
8734
        }
8735
8736
        $form = new FormValidator(
8737
            'edit_link',
8738
            'POST',
8739
            $this->getCurrentBuildingModeURL()
8740
        );
8741
        $defaults = [];
8742
        if ($id != 0 && is_array($extra_info)) {
8743
            $parent = $extra_info['parent_item_id'];
8744
        } else {
8745
            $parent = 0;
8746
        }
8747
8748
        $sql = "SELECT * FROM $tbl_lp_item
8749
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8750
        $result = Database::query($sql);
8751
        $arrLP = [];
8752
8753
        while ($row = Database::fetch_array($result)) {
8754
            $arrLP[] = array(
8755
                'id' => $row['id'],
8756
                'item_type' => $row['item_type'],
8757
                'title' => $row['title'],
8758
                'path' => $row['path'],
8759
                'description' => $row['description'],
8760
                'parent_item_id' => $row['parent_item_id'],
8761
                'previous_item_id' => $row['previous_item_id'],
8762
                'next_item_id' => $row['next_item_id'],
8763
                'display_order' => $row['display_order'],
8764
                'max_score' => $row['max_score'],
8765
                'min_score' => $row['min_score'],
8766
                'mastery_score' => $row['mastery_score'],
8767
                'prerequisite' => $row['prerequisite']
8768
            );
8769
        }
8770
8771
        $this->tree_array($arrLP);
8772
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
8773
        unset($this->arrMenu);
8774
8775
        if ($action == 'add') {
8776
            $legend = get_lang('CreateTheLink');
8777
        } elseif ($action == 'move') {
8778
            $legend = get_lang('MoveCurrentLink');
8779
        } else {
8780
            $legend = get_lang('EditCurrentLink');
8781
        }
8782
8783
        $form->addHeader($legend);
8784
8785
        if ($action != 'move') {
8786
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
8787
            $defaults['title'] = $item_title;
8788
        }
8789
8790
        $selectParent = $form->addSelect(
8791
            'parent',
8792
            get_lang('Parent'),
8793
            [],
8794
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8795
        );
8796
        $selectParent->addOption($this->name, 0);
8797
        $arrHide = array(
8798
            $id
8799
        );
8800
8801
        $parent_item_id = Session::read('parent_item_id', 0);
8802
8803
        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...
8804
            if ($action != 'add') {
8805
                if (
8806
                    ($arrLP[$i]['item_type'] == 'dir') &&
8807
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8808
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8809
                ) {
8810
                    $selectParent->addOption(
8811
                        $arrLP[$i]['title'],
8812
                        $arrLP[$i]['id'],
8813
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
8814
                    );
8815
8816
                    if ($parent == $arrLP[$i]['id']) {
8817
                        $selectParent->setSelected($arrLP[$i]['id']);
8818
                    }
8819
                } else {
8820
                    $arrHide[] = $arrLP[$i]['id'];
8821
                }
8822
            } else {
8823
                if ($arrLP[$i]['item_type'] == 'dir') {
8824
                    $selectParent->addOption(
8825
                        $arrLP[$i]['title'],
8826
                        $arrLP[$i]['id'],
8827
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8828
                    );
8829
8830
                    if ($parent_item_id == $arrLP[$i]['id']) {
8831
                        $selectParent->setSelected($arrLP[$i]['id']);
8832
                    }
8833
                }
8834
            }
8835
        }
8836
8837
        if (is_array($arrLP)) {
8838
            reset($arrLP);
8839
        }
8840
8841
        $selectPrevious = $form->addSelect(
8842
            'previous',
8843
            get_lang('Position'),
8844
            [],
8845
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8846
        );
8847
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8848
8849
        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...
8850
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8851
                $selectPrevious->addOption(
8852
                    $arrLP[$i]['title'],
8853
                    $arrLP[$i]['id']
8854
                );
8855
8856
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8857
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8858
                } elseif ($action == 'add') {
8859
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8860
                }
8861
            }
8862
        }
8863
8864
        if ($action != 'move') {
8865
            $urlAttributes = ['class' => 'learnpath_item_form'];
8866
8867
            if (is_numeric($extra_info)) {
8868
                $urlAttributes['disabled'] = 'disabled';
8869
            }
8870
8871
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
8872
            $defaults['url'] = $item_url;
8873
8874
            $id_prerequisite = 0;
8875
            if (is_array($arrLP)) {
8876
                foreach ($arrLP as $key => $value) {
8877
                    if ($value['id'] == $id) {
8878
                        $id_prerequisite = $value['prerequisite'];
8879
                        break;
8880
                    }
8881
                }
8882
            }
8883
8884
            $arrHide = [];
8885
            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...
8886
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8887
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id'])
8888
                        $s_selected_position = $arrLP[$i]['id'];
8889
                    elseif ($action == 'add') $s_selected_position = 0;
8890
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8891
8892
                }
8893
            }
8894
        }
8895
8896
        if ($action == 'add') {
8897
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
8898
        } else {
8899
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
8900
        }
8901
8902
        if ($action == 'move') {
8903
            $form->addHidden('title', $item_title);
8904
            $form->addHidden('description', $item_description);
8905
        }
8906
8907
        if (is_numeric($extra_info)) {
8908
            $form->addHidden('path', $extra_info);
8909
        } elseif (is_array($extra_info)) {
8910
            $form->addHidden('path', $extra_info['path']);
8911
        }
8912
        $form->addHidden('type', TOOL_LINK);
8913
        $form->addHidden('post_time', time());
8914
        $form->setDefaults($defaults);
8915
8916
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8917
    }
8918
8919
    /**
8920
     * Return HTML form to add/edit a student publication (work)
8921
     * @param	string	Action (add/edit)
8922
     * @param	integer	Item ID if already exists
8923
     * @param	mixed	Extra info (work ID if integer)
8924
     * @return	string	HTML form
8925
     */
8926
    public function display_student_publication_form(
8927
        $action = 'add',
8928
        $id = 0,
8929
        $extra_info = ''
8930
    ) {
8931
        $course_id = api_get_course_int_id();
8932
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8933
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
8934
8935
        if ($id != 0 && is_array($extra_info)) {
8936
            $item_title = stripslashes($extra_info['title']);
8937
            $item_description = stripslashes($extra_info['description']);
8938
        } elseif (is_numeric($extra_info)) {
8939
            $extra_info = intval($extra_info);
8940
            $sql = "SELECT title, description
8941
                    FROM $tbl_publication
8942
                    WHERE c_id = $course_id AND id = ".$extra_info;
8943
8944
            $result = Database::query($sql);
8945
            $row = Database::fetch_array($result);
8946
8947
            $item_title = $row['title'];
8948
        } else {
8949
            $item_title = get_lang('Student_publication');
8950
        }
8951
8952
        if ($id != 0 && is_array($extra_info)) {
8953
            $parent = $extra_info['parent_item_id'];
8954
        } else {
8955
            $parent = 0;
8956
        }
8957
8958
        $sql = "SELECT * FROM $tbl_lp_item 
8959
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8960
        $result = Database::query($sql);
8961
        $arrLP = [];
8962
8963
        while ($row = Database::fetch_array($result)) {
8964
            $arrLP[] = array(
8965
                'id' => $row['iid'],
8966
                'item_type' => $row['item_type'],
8967
                'title' => $row['title'],
8968
                'path' => $row['path'],
8969
                'description' => $row['description'],
8970
                'parent_item_id' => $row['parent_item_id'],
8971
                'previous_item_id' => $row['previous_item_id'],
8972
                'next_item_id' => $row['next_item_id'],
8973
                'display_order' => $row['display_order'],
8974
                'max_score' => $row['max_score'],
8975
                'min_score' => $row['min_score'],
8976
                'mastery_score' => $row['mastery_score'],
8977
                'prerequisite' => $row['prerequisite']
8978
            );
8979
        }
8980
8981
        $this->tree_array($arrLP);
8982
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
8983
        unset($this->arrMenu);
8984
8985
        $form = new FormValidator('frm_student_publication', 'post', '#');
8986
8987
        if ($action == 'add') {
8988
            $form->addHeader(get_lang('Student_publication'));
8989
        } elseif ($action == 'move') {
8990
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
8991
        } else {
8992
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
8993
        }
8994
8995
        if ($action != 'move') {
8996
            $form->addText(
8997
                'title',
8998
                get_lang('Title'),
8999
                true,
9000
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9001
            );
9002
        }
9003
9004
        $parentSelect = $form->addSelect(
9005
            'parent',
9006
            get_lang('Parent'),
9007
            ['0' => $this->name],
9008
            [
9009
                'onchange' => 'javascript: load_cbo(this.value);',
9010
                'class' => 'learnpath_item_form',
9011
                'id' => 'idParent'
9012
            ]
9013
        );
9014
9015
        $arrHide = array($id);
9016
        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...
9017
            if ($action != 'add') {
9018
                if (
9019
                    ($arrLP[$i]['item_type'] == 'dir') &&
9020
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9021
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9022
                ) {
9023
                    $parentSelect->addOption(
9024
                        $arrLP[$i]['title'],
9025
                        $arrLP[$i]['id'],
9026
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9027
                    );
9028
9029
                    if ($parent == $arrLP[$i]['id']) {
9030
                        $parentSelect->setSelected($arrLP[$i]['id']);
9031
                    }
9032
                } else {
9033
                    $arrHide[] = $arrLP[$i]['id'];
9034
                }
9035
            } else {
9036
                if ($arrLP[$i]['item_type'] == 'dir') {
9037
                    $parentSelect->addOption(
9038
                        $arrLP[$i]['title'],
9039
                        $arrLP[$i]['id'],
9040
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9041
                    );
9042
9043
                    if ($parent == $arrLP[$i]['id']) {
9044
                        $parentSelect->setSelected($arrLP[$i]['id']);
9045
                    }
9046
                }
9047
            }
9048
        }
9049
9050
        if (is_array($arrLP)) {
9051
            reset($arrLP);
9052
        }
9053
9054
        $previousSelect = $form->addSelect(
9055
            'previous',
9056
            get_lang('Position'),
9057
            ['0' => get_lang('FirstPosition')],
9058
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9059
        );
9060
9061
        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...
9062
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9063
                $previousSelect->addOption(
9064
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9065
                    $arrLP[$i]['id']
9066
                );
9067
9068
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9069
                    $previousSelect->setSelected($arrLP[$i]['id']);
9070
                } elseif ($action == 'add') {
9071
                    $previousSelect->setSelected($arrLP[$i]['id']);
9072
                }
9073
            }
9074
        }
9075
9076
        if ($action != 'move') {
9077
            $id_prerequisite = 0;
9078
            if (is_array($arrLP)) {
9079
                foreach ($arrLP as $key => $value) {
9080
                    if ($value['id'] == $id) {
9081
                        $id_prerequisite = $value['prerequisite'];
9082
                        break;
9083
                    }
9084
                }
9085
            }
9086
            $arrHide = [];
9087
            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...
9088
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9089
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id'])
9090
                        $s_selected_position = $arrLP[$i]['id'];
9091
                    elseif ($action == 'add') $s_selected_position = 0;
9092
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9093
9094
                }
9095
            }
9096
        }
9097
9098
        if ($action == 'add') {
9099
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9100
        } else {
9101
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9102
        }
9103
9104
        if ($action == 'move') {
9105
            $form->addHidden('title', $item_title);
9106
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
9107
        }
9108
9109
        if (is_numeric($extra_info)) {
9110
            $form->addHidden('path', $extra_info);
9111
        } elseif (is_array($extra_info)) {
9112
            $form->addHidden('path', $extra_info['path']);
9113
        }
9114
9115
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9116
        $form->addHidden('post_time', time());
9117
        $form->setDefaults(['title' => $item_title]);
9118
9119
        $return = '<div class="sectioncomment">';
9120
        $return .= $form->returnForm();
9121
        $return .= '</div>';
9122
9123
        return $return;
9124
    }
9125
9126
    /**
9127
     * Displays the menu for manipulating a step
9128
     *
9129
     * @param $item_id
9130
     * @param string $item_type
9131
     * @return string
9132
     */
9133
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9134
    {
9135
        $_course = api_get_course_info();
9136
        $course_id = api_get_course_int_id();
9137
        $course_code = api_get_course_id();
9138
        $return = '<div class="actions">';
9139
        switch ($item_type) {
9140
            case 'dir':
9141
                // Commented the message cause should not show it.
9142
                //$lang = get_lang('TitleManipulateChapter');
9143
                break;
9144
            case TOOL_LP_FINAL_ITEM:
9145
            case TOOL_DOCUMENT:
9146
                // Commented the message cause should not show it.
9147
                //$lang = get_lang('TitleManipulateDocument');
9148
                break;
9149
            case TOOL_LINK:
9150
            case 'link' :
9151
                // Commented the message cause should not show it.
9152
                //$lang = get_lang('TitleManipulateLink');
9153
                break;
9154
            case TOOL_QUIZ:
9155
                // Commented the message cause should not show it.
9156
                //$lang = get_lang('TitleManipulateQuiz');
9157
                break;
9158
            case TOOL_STUDENTPUBLICATION:
9159
                // Commented the message cause should not show it.
9160
                //$lang = get_lang('TitleManipulateStudentPublication');
9161
                break;
9162
        }
9163
9164
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9165
        $item_id = intval($item_id);
9166
        $sql = "SELECT * FROM $tbl_lp_item 
9167
                WHERE iid = ".$item_id;
9168
        $result = Database::query($sql);
9169
        $row = Database::fetch_assoc($result);
9170
9171
        $audio_player = null;
9172
        // We display an audio player if needed.
9173
        if (!empty($row['audio'])) {
9174
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9175
                              <a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.
9176
                              </div>';
9177
            $audio_player .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>';
9178
            $audio_player .= '<script>
9179
                var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF");
9180
                s1.addParam("allowscriptaccess","always");
9181
                s1.addParam("flashvars","file=../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'].'&autostart=true");
9182
                s1.write("container");
9183
            </script>';
9184
        }
9185
9186
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9187
9188
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9189
            $return .= Display::url(
9190
                Display::return_icon(
9191
                    'edit.png',
9192
                    get_lang('Edit'),
9193
                    [],
9194
                    ICON_SIZE_SMALL
9195
                ),
9196
                $url.'&action=edit_item&path_item='.$row['path']
9197
            );
9198
9199
            $return .= Display::url(
9200
                Display::return_icon(
9201
                    'move.png',
9202
                    get_lang('Move'),
9203
                    [],
9204
                    ICON_SIZE_SMALL
9205
                ),
9206
                $url.'&action=move_item'
9207
            );
9208
        }
9209
9210
        // Commented for now as prerequisites cannot be added to chapters.
9211
        if ($item_type != 'dir') {
9212
            $return .= Display::url(
9213
                Display::return_icon(
9214
                    'accept.png',
9215
                    get_lang('LearnpathPrerequisites'),
9216
                    [],
9217
                    ICON_SIZE_SMALL
9218
                ),
9219
                $url.'&action=edit_item_prereq'
9220
            );
9221
        }
9222
        $return .= Display::url(
9223
            Display::return_icon(
9224
                'delete.png',
9225
                get_lang('Delete'),
9226
                [],
9227
                ICON_SIZE_SMALL
9228
            ),
9229
            $url.'&action=delete_item'
9230
        );
9231
9232
        if ($item_type == TOOL_HOTPOTATOES) {
9233
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9234
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9235
        }
9236
9237
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
9238
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9239
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9240
        }
9241
9242
        $return .= '</div>';
9243
9244
        if (!empty($audio_player)) {
9245
            $return .= '<br />'.$audio_player;
9246
        }
9247
9248
        return $return;
9249
    }
9250
9251
    /**
9252
     * Creates the javascript needed for filling up the checkboxes without page reload
9253
     * @return string
9254
     */
9255
    public function get_js_dropdown_array()
9256
    {
9257
        $course_id = api_get_course_int_id();
9258
        $return = 'var child_name = new Array();'."\n";
9259
        $return .= 'var child_value = new Array();'."\n\n";
9260
        $return .= 'child_name[0] = new Array();'."\n";
9261
        $return .= 'child_value[0] = new Array();'."\n\n";
9262
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9263
        $sql = "SELECT * FROM ".$tbl_lp_item."
9264
                WHERE 
9265
                    c_id = $course_id AND 
9266
                    lp_id = ".$this->lp_id." AND 
9267
                    parent_item_id = 0
9268
                ORDER BY display_order ASC";
9269
        $res_zero = Database::query($sql);
9270
        $i = 0;
9271
9272
        while ($row_zero = Database::fetch_array($res_zero)) {
9273
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9274
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9275
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9276
                }
9277
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9278
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9279
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9280
            }
9281
        }
9282
        $return .= "\n";
9283
        $sql = "SELECT * FROM $tbl_lp_item
9284
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9285
        $res = Database::query($sql);
9286
        while ($row = Database::fetch_array($res)) {
9287
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9288
                           WHERE
9289
                                c_id = ".$course_id." AND
9290
                                parent_item_id = " . $row['iid']."
9291
                           ORDER BY display_order ASC";
9292
            $res_parent = Database::query($sql_parent);
9293
            $i = 0;
9294
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9295
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9296
9297
            while ($row_parent = Database::fetch_array($res_parent)) {
9298
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9299
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9300
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9301
            }
9302
            $return .= "\n";
9303
        }
9304
9305
        return $return;
9306
    }
9307
9308
    /**
9309
     * Display the form to allow moving an item
9310
     * @param	integer $item_id		Item ID
9311
     * @return	string		HTML form
9312
     */
9313
    public function display_move_item($item_id)
9314
    {
9315
        $course_id = api_get_course_int_id();
9316
        $return = '';
9317
9318
        if (is_numeric($item_id)) {
9319
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9320
9321
            $sql = "SELECT * FROM $tbl_lp_item
9322
                    WHERE iid = $item_id";
9323
            $res = Database::query($sql);
9324
            $row = Database::fetch_array($res);
9325
9326
            switch ($row['item_type']) {
9327
                case 'dir':
9328
                case 'asset':
9329
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9330
                    $return .= $this->display_item_form(
9331
                        $row['item_type'],
9332
                        get_lang('MoveCurrentChapter'),
9333
                        'move',
9334
                        $item_id,
9335
                        $row
9336
                    );
9337
                    break;
9338
                case TOOL_DOCUMENT:
9339
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9340
                    $return .= $this->display_document_form('move', $item_id, $row);
9341
                    break;
9342
                case TOOL_LINK:
9343
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9344
                    $return .= $this->display_link_form('move', $item_id, $row);
9345
                    break;
9346
                case TOOL_HOTPOTATOES:
9347
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9348
                    $return .= $this->display_link_form('move', $item_id, $row);
9349
                    break;
9350
                case TOOL_QUIZ:
9351
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9352
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9353
                    break;
9354
                case TOOL_STUDENTPUBLICATION:
9355
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9356
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9357
                    break;
9358
                case TOOL_FORUM:
9359
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9360
                    $return .= $this->display_forum_form('move', $item_id, $row);
9361
                    break;
9362
                case TOOL_THREAD:
9363
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9364
                    $return .= $this->display_forum_form('move', $item_id, $row);
9365
                    break;
9366
            }
9367
        }
9368
9369
        return $return;
9370
    }
9371
9372
    /**
9373
     * Displays a basic form on the overview page for changing the item title and the item description.
9374
     * @param string $item_type
9375
     * @param string $title
9376
     * @param array $data
9377
     * @return string
9378
     */
9379
    public function display_item_small_form($item_type, $title = '', $data = [])
9380
    {
9381
        $url = api_get_self().'?'.api_get_cidreq().'&action=edit_item&lp_id='.$this->lp_id;
9382
        $form = new FormValidator('small_form', 'post', $url);
9383
        $form->addElement('header', $title);
9384
        $form->addElement('text', 'title', get_lang('Title'));
9385
        $form->addButtonSave(get_lang('Save'), 'submit_button');
9386
        $form->addElement('hidden', 'id', $data['id']);
9387
        $form->addElement('hidden', 'parent', $data['parent_item_id']);
9388
        $form->addElement('hidden', 'previous', $data['previous_item_id']);
9389
        $form->setDefaults(array('title' => $data['title']));
9390
9391
        return $form->toHtml();
9392
    }
9393
9394
    /**
9395
     * Return HTML form to allow prerequisites selection
9396
     * @todo use FormValidator
9397
     * @param	integer Item ID
9398
     * @return	string	HTML form
9399
     */
9400
    public function display_item_prerequisites_form($item_id)
9401
    {
9402
        $course_id = api_get_course_int_id();
9403
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9404
        $item_id = intval($item_id);
9405
        /* Current prerequisite */
9406
        $sql = "SELECT * FROM $tbl_lp_item
9407
                WHERE iid = $item_id";
9408
        $result = Database::query($sql);
9409
        $row    = Database::fetch_array($result);
9410
        $prerequisiteId = $row['prerequisite'];
9411
        $return = '<legend>';
9412
        $return .= get_lang('AddEditPrerequisites');
9413
        $return .= '</legend>';
9414
        $return .= '<form method="POST">';
9415
        $return .= '<div class="table-responsive">';
9416
        $return .= '<table class="table table-hover">';
9417
        $return .= '<thead>';
9418
        $return .= '<tr>';
9419
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9420
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9421
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9422
        $return .= '</tr>';
9423
        $return .= '</thead>';
9424
9425
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9426
        $return .= '<tbody>';
9427
        $return .= '<tr>';
9428
        $return .= '<td colspan="3">';
9429
        $return .= '<div class="radio learnpath"><label for="idNone">';
9430
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9431
        $return .= get_lang('None').'</label>';
9432
        $return .= '</div>';
9433
        $return .= '</tr>';
9434
9435
        $sql = "SELECT * FROM $tbl_lp_item
9436
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9437
        $result = Database::query($sql);
9438
        $arrLP = [];
9439
9440
        $selectedMinScore = [];
9441
        $selectedMaxScore = [];
9442
        while ($row = Database::fetch_array($result)) {
9443
            if ($row['id'] == $item_id) {
9444
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9445
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9446
            }
9447
            $arrLP[] = array(
9448
                'id' => $row['iid'],
9449
                'item_type' => $row['item_type'],
9450
                'title' => $row['title'],
9451
                'ref' => $row['ref'],
9452
                'description' => $row['description'],
9453
                'parent_item_id' => $row['parent_item_id'],
9454
                'previous_item_id' => $row['previous_item_id'],
9455
                'next_item_id' => $row['next_item_id'],
9456
                'max_score' => $row['max_score'],
9457
                'min_score' => $row['min_score'],
9458
                'mastery_score' => $row['mastery_score'],
9459
                'prerequisite' => $row['prerequisite'],
9460
                'next_item_id' => $row['next_item_id'],
9461
                'display_order' => $row['display_order'],
9462
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9463
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9464
            );
9465
        }
9466
9467
        $this->tree_array($arrLP);
9468
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
9469
        unset($this->arrMenu);
9470
9471
        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...
9472
            $item = $arrLP[$i];
9473
9474
            if ($item['id'] == $item_id) {
9475
                break;
9476
            }
9477
9478
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9479
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9480
9481
            $return .= '<tr>';
9482
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9483
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9484
            $return .= '<label for="id'.$item['id'].'">';
9485
            $return .= '<input'.(in_array($prerequisiteId, array($item['id'], $item['ref'])) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
9486
9487
            $icon_name = str_replace(' ', '', $item['item_type']);
9488
9489
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9490
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9491
            } else {
9492
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9493
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9494
                } else {
9495
                    $return .= Display::return_icon('folder_document.png');
9496
                }
9497
            }
9498
9499
            $return .= $item['title'].'</label>';
9500
            $return .= '</div>';
9501
            $return .= '</td>';
9502
9503
            if ($item['item_type'] == TOOL_QUIZ) {
9504
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9505
                $tmp_obj_lp_item = new LpItem($course_id, $item['id']);
9506
                $tmp_obj_exercice = new Exercise($course_id);
9507
                $tmp_obj_exercice->read($tmp_obj_lp_item->path);
9508
                $tmp_obj_lp_item->max_score = $tmp_obj_exercice->get_max_score();
9509
                $tmp_obj_lp_item->update();
9510
                $item['max_score'] = $tmp_obj_lp_item->max_score;
9511
9512
                $return .= '<td>';
9513
                $return .= '<input class="form-control" size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
9514
                $return .= '</td>';
9515
                $return .= '<td>';
9516
                $return .= '<input class="form-control" size="4" maxlength="3" readonly name="max_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMaxScoreValue.'" />';
9517
                $return .= '</td>';
9518
            }
9519
9520
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9521
                $return .= '<td>';
9522
                $return .= '<input size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
9523
                $return .= '</td>';
9524
                $return .= '<td>';
9525
                $return .= '<input size="4" maxlength="3" name="max_'.$item['id'].'" readonly type="number" min="0" step="1" max="'.$item['max_score'].'"  value="'.$selectedMaxScoreValue.'" />';
9526
                $return .= '</td>';
9527
            }
9528
            $return .= '</tr>';
9529
        }
9530
        $return .= '<tr>';
9531
        $return .= '</tr>';
9532
        $return .= '</tbody>';
9533
        $return .= '</table>';
9534
        $return .= '</div>';
9535
        $return .= '<div class="form-group">';
9536
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.get_lang('ModifyPrerequisites').'</button>';
9537
        $return .= '</form>';
9538
9539
        return $return;
9540
    }
9541
9542
    /**
9543
     * Return HTML list to allow prerequisites selection for lp
9544
     * @param	integer Item ID
9545
     * @return	string	HTML form
9546
     */
9547
    public function display_lp_prerequisites_list()
9548
    {
9549
        $course_id = api_get_course_int_id();
9550
        $lp_id = $this->lp_id;
9551
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9552
9553
        // get current prerequisite
9554
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9555
        $result = Database::query($sql);
9556
        $row = Database::fetch_array($result);
9557
        $prerequisiteId = $row['prerequisite'];
9558
        $session_id = api_get_session_id();
9559
        $session_condition = api_get_session_condition($session_id, true, true);
9560
        $sql = "SELECT * FROM $tbl_lp
9561
                WHERE c_id = $course_id $session_condition
9562
                ORDER BY display_order ";
9563
        $rs = Database::query($sql);
9564
        $return = '';
9565
        $return .= '<select name="prerequisites" class="form-control">';
9566
        $return .= '<option value="0">'.get_lang('None').'</option>';
9567
        if (Database::num_rows($rs) > 0) {
9568
            while ($row = Database::fetch_array($rs)) {
9569
                if ($row['id'] == $lp_id) {
9570
                    continue;
9571
                }
9572
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9573
            }
9574
        }
9575
        $return .= '</select>';
9576
9577
        return $return;
9578
    }
9579
9580
    /**
9581
     * Creates a list with all the documents in it
9582
     * @param bool $showInvisibleFiles
9583
     * @return string
9584
     */
9585
    public function get_documents($showInvisibleFiles = false)
9586
    {
9587
        $course_info = api_get_course_info();
9588
        $sessionId = api_get_session_id();
9589
        $documentTree = DocumentManager::get_document_preview(
9590
            $course_info,
9591
            $this->lp_id,
9592
            null,
9593
            $sessionId,
9594
            true,
9595
            null,
9596
            null,
9597
            $showInvisibleFiles,
9598
            true
9599
        );
9600
9601
        $headers = array(
9602
            get_lang('Files'),
9603
            get_lang('CreateTheDocument'),
9604
            get_lang('Upload')
9605
        );
9606
9607
        $form = new FormValidator(
9608
            'form_upload',
9609
            'POST',
9610
            $this->getCurrentBuildingModeURL(),
9611
            '',
9612
            array('enctype' => 'multipart/form-data')
9613
        );
9614
9615
        $folders = DocumentManager::get_all_document_folders(
9616
            api_get_course_info(),
9617
            0,
9618
            true
9619
        );
9620
9621
        DocumentManager::build_directory_selector(
9622
            $folders,
9623
            '',
9624
            [],
9625
            true,
9626
            $form,
9627
            'directory_parent_id'
9628
        );
9629
9630
        $group = array(
9631
            $form->createElement(
9632
                'radio',
9633
                'if_exists',
9634
                get_lang("UplWhatIfFileExists"),
9635
                get_lang('UplDoNothing'),
9636
                'nothing'
9637
            ),
9638
            $form->createElement(
9639
                'radio',
9640
                'if_exists',
9641
                null,
9642
                get_lang('UplOverwriteLong'),
9643
                'overwrite'
9644
            ),
9645
            $form->createElement(
9646
                'radio',
9647
                'if_exists',
9648
                null,
9649
                get_lang('UplRenameLong'),
9650
                'rename'
9651
            )
9652
        );
9653
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
9654
        $form->setDefaults(['if_exists' => 'rename']);
9655
9656
        // Check box options
9657
        $form->addElement(
9658
            'checkbox',
9659
            'unzip',
9660
            get_lang('Options'),
9661
            get_lang('Uncompress')
9662
        );
9663
9664
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9665
        $form->addMultipleUpload($url);
9666
        $new = $this->display_document_form('add', 0);
9667
        $tabs = Display::tabs(
9668
            $headers,
9669
            array($documentTree, $new, $form->returnForm()),
9670
            'subtab'
9671
        );
9672
9673
        return $tabs;
9674
    }
9675
9676
    /**
9677
     * Creates a list with all the exercises (quiz) in it
9678
     * @return string
9679
     */
9680
    public function get_exercises()
9681
    {
9682
        $course_id = api_get_course_int_id();
9683
        $session_id = api_get_session_id();
9684
        $userInfo = api_get_user_info();
9685
9686
        // New for hotpotatoes.
9687
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
9688
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9689
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9690
        $condition_session = api_get_session_condition($session_id, true, true);
9691
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
9692
9693
        $activeCondition = ' active <> -1 ';
9694
        if ($setting) {
9695
            $activeCondition = ' active = 1 ';
9696
        }
9697
9698
        $sql_quiz = "SELECT * FROM $tbl_quiz
9699
                     WHERE c_id = $course_id AND $activeCondition $condition_session
9700
                     ORDER BY title ASC";
9701
9702
        $sql_hot = "SELECT * FROM $tbl_doc
9703
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
9704
                     ORDER BY id ASC";
9705
9706
        $res_quiz = Database::query($sql_quiz);
9707
        $res_hot  = Database::query($sql_hot);
9708
9709
        $return = '<ul class="lp_resource">';
9710
        $return .= '<li class="lp_resource_element">';
9711
        $return .= Display::return_icon('new_exercice.png');
9712
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
9713
            get_lang('NewExercise').'</a>';
9714
        $return .= '</li>';
9715
9716
        $previewIcon = Display::return_icon(
9717
            'preview_view.png',
9718
            get_lang('Preview')
9719
        );
9720
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
9721
9722
        // Display hotpotatoes
9723
        while ($row_hot = Database::fetch_array($res_hot)) {
9724
            $link = Display::url(
9725
                $previewIcon,
9726
                $exerciseUrl.'&file='.$row_hot['path'],
9727
                ['target' => '_blank']
9728
            );
9729
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
9730
            $return .= '<a class="moved" href="#">';
9731
            $return .= Display::return_icon(
9732
                'move_everywhere.png',
9733
                get_lang('Move'),
9734
                [],
9735
                ICON_SIZE_TINY
9736
            );
9737
            $return .= '</a> ';
9738
            $return .= Display::return_icon('hotpotatoes_s.png');
9739
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
9740
                ((!empty ($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
9741
            $return .= '</li>';
9742
        }
9743
9744
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
9745
        while ($row_quiz = Database::fetch_array($res_quiz)) {
9746
            $title = strip_tags(
9747
                api_html_entity_decode($row_quiz['title'])
9748
            );
9749
9750
            $link = Display::url(
9751
                $previewIcon,
9752
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
9753
                ['target' => '_blank']
9754
            );
9755
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
9756
            $return .= '<a class="moved" href="#">';
9757
            $return .= Display::return_icon(
9758
                'move_everywhere.png',
9759
                get_lang('Move'),
9760
                [],
9761
                ICON_SIZE_TINY
9762
            );
9763
            $return .= '</a> ';
9764
            $return .= Display::return_icon(
9765
                'quizz_small.gif',
9766
                '',
9767
                [],
9768
                ICON_SIZE_TINY
9769
            );
9770
            $sessionStar = api_get_session_image(
9771
                $row_quiz['session_id'],
9772
                $userInfo['status']
9773
            );
9774
            $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id.'">'.
9775
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
9776
                '</a>';
9777
9778
            $return .= '</li>';
9779
        }
9780
9781
        $return .= '</ul>';
9782
        return $return;
9783
    }
9784
9785
    /**
9786
     * Creates a list with all the links in it
9787
     * @return string
9788
     */
9789
    public function get_links()
9790
    {
9791
        $selfUrl = api_get_self();
9792
        $courseIdReq = api_get_cidreq();
9793
        $course = api_get_course_info();
9794
        $userInfo = api_get_user_info();
9795
9796
        $course_id = $course['real_id'];
9797
        $tbl_link = Database::get_course_table(TABLE_LINK);
9798
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
9799
        $moveEverywhereIcon = Display::return_icon(
9800
            'move_everywhere.png',
9801
            get_lang('Move'),
9802
            [],
9803
            ICON_SIZE_TINY
9804
        );
9805
9806
        $session_id = api_get_session_id();
9807
        $condition_session = api_get_session_condition(
9808
            $session_id,
9809
            true,
9810
            true,
9811
            "link.session_id"
9812
        );
9813
9814
        $sql = "SELECT 
9815
                    link.id as link_id,
9816
                    link.title as link_title,
9817
                    link.session_id as link_session_id,
9818
                    link.category_id as category_id,
9819
                    link_category.category_title as category_title
9820
                FROM $tbl_link as link
9821
                LEFT JOIN $linkCategoryTable as link_category
9822
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
9823
                WHERE link.c_id = ".$course_id." $condition_session
9824
                ORDER BY link_category.category_title ASC, link.title ASC";
9825
        $result = Database::query($sql);
9826
        $categorizedLinks = [];
9827
        $categories = [];
9828
9829
        while ($link = Database::fetch_array($result)) {
9830
            if (!$link['category_id']) {
9831
                $link['category_title'] = get_lang('Uncategorized');
9832
            }
9833
            $categories[$link['category_id']] = $link['category_title'];
9834
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
9835
        }
9836
9837
        $linksHtmlCode =
9838
            '<script>
9839
            function toggle_tool(tool, id) {
9840
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
9841
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
9842
                    document.getElementById(tool+"_"+id+"_opener").src = "' . Display::returnIconPath('remove.gif').'";
9843
                } else {
9844
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
9845
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
9846
                }
9847
            }
9848
        </script>
9849
9850
        <ul class="lp_resource">
9851
            <li class="lp_resource_element">
9852
                '.Display::return_icon('linksnew.gif').'
9853
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
9854
                get_lang('LinkAdd').'
9855
                </a>
9856
            </li>';
9857
9858
        foreach ($categorizedLinks as $categoryId => $links) {
9859
            $linkNodes = null;
9860
            foreach ($links as $key => $linkInfo) {
9861
                $title = $linkInfo['link_title'];
9862
                $linkSessionId = $linkInfo['link_session_id'];
9863
9864
                $link = Display::url(
9865
                    Display::return_icon('preview_view.png', get_lang('Preview')),
9866
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
9867
                    ['target' => '_blank']
9868
                );
9869
9870
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
9871
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
9872
                    $linkNodes .=
9873
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
9874
                        <a class="moved" href="#">'.
9875
                            $moveEverywhereIcon.
9876
                        '</a>
9877
                        '.Display::return_icon('lp_link.png').'
9878
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
9879
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
9880
                        Security::remove_XSS($title).$sessionStar.$link.
9881
                        '</a>
9882
                    </li>';
9883
                }
9884
            }
9885
            $linksHtmlCode .=
9886
                '<li>
9887
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
9888
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
9889
                    align="absbottom" />
9890
                </a>
9891
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
9892
            </li>
9893
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
9894
        }
9895
        $linksHtmlCode .= '</ul>';
9896
9897
        return $linksHtmlCode;
9898
    }
9899
9900
    /**
9901
     * Creates a list with all the student publications in it
9902
     * @return string
9903
     */
9904
    public function get_student_publications()
9905
    {
9906
        $return = '<ul class="lp_resource">';
9907
        $return .= '<li class="lp_resource_element">';
9908
        $return .= Display::return_icon('works_new.gif');
9909
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
9910
            get_lang('AddAssignmentPage').'</a>';
9911
        $return .= '</li>';
9912
        $sessionId = api_get_session_id();
9913
9914
        if (empty($sessionId)) {
9915
            require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
9916
            $works = getWorkListTeacher(0, 100, null, null, null);
9917
            if (!empty($works)) {
9918
                foreach ($works as $work) {
9919
                    $link = Display::url(
9920
                        Display::return_icon('preview_view.png', get_lang('Preview')),
9921
                        api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
9922
                        ['target' => '_blank']
9923
                    );
9924
9925
                    $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
9926
                    $return .= '<a class="moved" href="#">';
9927
                    $return .= Display::return_icon(
9928
                        'move_everywhere.png',
9929
                        get_lang('Move'),
9930
                        [],
9931
                        ICON_SIZE_TINY
9932
                    );
9933
                    $return .= '</a> ';
9934
9935
                    $return .= Display::return_icon('works.gif');
9936
                    $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.'">'.
9937
                        Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
9938
                    </a>';
9939
9940
                    $return .= '</li>';
9941
                }
9942
            }
9943
        }
9944
9945
        $return .= '</ul>';
9946
9947
        return $return;
9948
    }
9949
9950
    /**
9951
     * Creates a list with all the forums in it
9952
     * @return string
9953
     */
9954
    public function get_forums()
9955
    {
9956
        require_once '../forum/forumfunction.inc.php';
9957
        require_once '../forum/forumconfig.inc.php';
9958
9959
        $forumCategories = get_forum_categories();
9960
        $forumsInNoCategory = get_forums_in_category(0);
9961
        if (!empty($forumsInNoCategory)) {
9962
            $forumCategories = array_merge(
9963
                $forumCategories,
9964
                array(
9965
                    array(
9966
                        'cat_id' => 0,
9967
                        'session_id' => 0,
9968
                        'visibility' => 1,
9969
                        'cat_comment' => null,
9970
                    ),
9971
                )
9972
            );
9973
        }
9974
9975
        $forumList = get_forums();
9976
        $a_forums = [];
9977
        foreach ($forumCategories as $forumCategory) {
9978
            // The forums in this category.
9979
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
9980
            if (!empty($forumsInCategory)) {
9981
                foreach ($forumList as $forum) {
9982
                    if (isset($forum['forum_category']) &&
9983
                        $forum['forum_category'] == $forumCategory['cat_id']
9984
                    ) {
9985
                        $a_forums[] = $forum;
9986
                    }
9987
                }
9988
            }
9989
        }
9990
9991
        $return = '<ul class="lp_resource">';
9992
9993
        // First add link
9994
        $return .= '<li class="lp_resource_element">';
9995
        $return .= Display::return_icon('new_forum.png');
9996
        $return .= Display::url(
9997
            get_lang('CreateANewForum'),
9998
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
9999
                'action' => 'add',
10000
                'content' => 'forum',
10001
                'lp_id' => $this->lp_id
10002
            ]),
10003
            ['title' => get_lang('CreateANewForum')]
10004
        );
10005
        $return .= '</li>';
10006
10007
        $return .= '<script>
10008
            function toggle_forum(forum_id) {
10009
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10010
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10011
                    document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('remove.gif').'";
10012
                } else {
10013
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10014
                    document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('add.gif').'";
10015
                }
10016
            }
10017
        </script>';
10018
10019
        foreach ($a_forums as $forum) {
10020
            if (!empty($forum['forum_id'])) {
10021
                $link = Display::url(
10022
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10023
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10024
                    ['target' => '_blank']
10025
                );
10026
10027
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10028
                $return .= '<a class="moved" href="#">';
10029
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10030
                $return .= ' </a>';
10031
                $return .= Display::return_icon('lp_forum.png', '', [], ICON_SIZE_TINY);
10032
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10033
                                <img src="' . Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10034
                            </a>
10035
                            <a class="moved" href="' . api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forum['forum_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
10036
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10037
10038
                $return .= '</li>';
10039
10040
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10041
                $a_threads = get_threads($forum['forum_id']);
10042
                if (is_array($a_threads)) {
10043
                    foreach ($a_threads as $thread) {
10044
                        $link = Display::url(
10045
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10046
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10047
                            ['target' => '_blank']
10048
                        );
10049
10050
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10051
                        $return .= '&nbsp;<a class="moved" href="#">';
10052
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10053
                        $return .= ' </a>';
10054
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10055
                        $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
10056
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10057
                        $return .= '</li>';
10058
                    }
10059
                }
10060
                $return .= '</div>';
10061
            }
10062
        }
10063
        $return .= '</ul>';
10064
10065
        return $return;
10066
    }
10067
10068
    /**
10069
     * // TODO: The output encoding should be equal to the system encoding.
10070
     *
10071
     * Exports the learning path as a SCORM package. This is the main function that
10072
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10073
     * whole thing and returns the zip.
10074
     *
10075
     * This method needs to be called in PHP5, as it will fail with non-adequate
10076
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10077
     * you need to call it on a learnpath object.
10078
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10079
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10080
     * path has been modified, it should use the generic method here below.
10081
     * @param	string	Optional name of zip file. If none, title of learnpath is
10082
     * 					domesticated and trailed with ".zip"
10083
     * @return	string	Returns the zip package string, or null if error
10084
     */
10085
    public function scorm_export()
10086
    {
10087
        $_course = api_get_course_info();
10088
        $course_id = $_course['real_id'];
10089
10090
        // Remove memory and time limits as much as possible as this might be a long process...
10091
        if (function_exists('ini_set')) {
10092
            api_set_memory_limit('256M');
10093
            ini_set('max_execution_time', 600);
10094
        }
10095
10096
        // Create the zip handler (this will remain available throughout the method).
10097
        $archive_path = api_get_path(SYS_ARCHIVE_PATH);
10098
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10099
        $temp_dir_short = uniqid();
10100
        $temp_zip_dir = $archive_path.'/'.$temp_dir_short;
10101
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10102
        $zip_folder = new PclZip($temp_zip_file);
10103
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10104
        $root_path = $main_path = api_get_path(SYS_PATH);
10105
        $files_cleanup = [];
10106
10107
        // Place to temporarily stash the zip file.
10108
        // create the temp dir if it doesn't exist
10109
        // or do a cleanup before creating the zip file.
10110
        if (!is_dir($temp_zip_dir)) {
10111
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10112
        } else {
10113
            // Cleanup: Check the temp dir for old files and delete them.
10114
            $handle = opendir($temp_zip_dir);
10115
            while (false !== ($file = readdir($handle))) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

10115
            while (false !== ($file = readdir(/** @scrutinizer ignore-type */ $handle))) {
Loading history...
10116
                if ($file != '.' && $file != '..') {
10117
                    unlink("$temp_zip_dir/$file");
10118
                }
10119
            }
10120
            closedir($handle);
10121
        }
10122
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10123
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10124
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10125
        ) {
10126
            // Remove the possible . at the end of the path.
10127
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10128
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10129
            mkdir(
10130
                $dest_path_to_scorm_folder,
10131
                api_get_permissions_for_new_directories(),
10132
                true
10133
            );
10134
            copyr(
10135
                $current_course_path.'/scorm/'.$this->path,
10136
                $dest_path_to_scorm_folder,
10137
                array('imsmanifest'),
10138
                $zip_files
10139
            );
10140
        }
10141
10142
        // Build a dummy imsmanifest structure.
10143
        // Do not add to the zip yet (we still need it).
10144
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10145
        // Aggregation Model official document, section "2.3 Content Packaging".
10146
        // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
10147
        $xmldoc = new DOMDocument('1.0');
0 ignored issues
show
Bug introduced by
The call to DOMDocument::__construct() has too few arguments starting with encoding. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

10147
        $xmldoc = /** @scrutinizer ignore-call */ new DOMDocument('1.0');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
10148
        $root = $xmldoc->createElement('manifest');
10149
        $root->setAttribute('identifier', 'SingleCourseManifest');
10150
        $root->setAttribute('version', '1.1');
10151
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10152
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10153
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10154
        $root->setAttribute('xsi:schemaLocation', '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');
10155
        // Build mandatory sub-root container elements.
10156
        $metadata = $xmldoc->createElement('metadata');
10157
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10158
        $metadata->appendChild($md_schema);
10159
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10160
        $metadata->appendChild($md_schemaversion);
10161
        $root->appendChild($metadata);
10162
10163
        $organizations = $xmldoc->createElement('organizations');
10164
        $resources = $xmldoc->createElement('resources');
10165
10166
        // Build the only organization we will use in building our learnpaths.
10167
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10168
        $organization = $xmldoc->createElement('organization');
10169
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10170
        // To set the title of the SCORM entity (=organization), we take the name given
10171
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10172
        // learning path charset) as it is the encoding that defines how it is stored
10173
        // in the database. Then we convert it to HTML entities again as the "&" character
10174
        // alone is not authorized in XML (must be &amp;).
10175
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10176
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10177
        $organization->appendChild($org_title);
10178
10179
        $folder_name = 'document';
10180
10181
        // Removes the learning_path/scorm_folder path when exporting see #4841
10182
        $path_to_remove = null;
10183
        $result = $this->generate_lp_folder($_course);
10184
10185
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10186
            $path_to_remove = 'document'.$result['dir'];
10187
            $path_to_replace = $folder_name.'/';
10188
        }
10189
10190
        // Fixes chamilo scorm exports
10191
        if ($this->ref === 'chamilo_scorm_export') {
10192
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10193
        }
10194
10195
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10196
        // Always call the learnpathItem->scorm_export() method to change it to the SCORM format.
10197
        $link_updates = [];
10198
        $links_to_create = [];
10199
        //foreach ($this->items as $index => $item) {
10200
        foreach ($this->ordered_items as $index => $itemId) {
10201
            $item = $this->items[$itemId];
10202
            if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) {
10203
                // Get included documents from this item.
10204
                if ($item->type === 'sco') {
10205
                    $inc_docs = $item->get_resources_from_source(
10206
                        null,
10207
                        api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()
10208
                    );
10209
                } else {
10210
                    $inc_docs = $item->get_resources_from_source();
10211
                }
10212
                // Give a child element <item> to the <organization> element.
10213
                $my_item_id = $item->get_id();
10214
                $my_item = $xmldoc->createElement('item');
10215
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10216
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10217
                $my_item->setAttribute('isvisible', 'true');
10218
                // Give a child element <title> to the <item> element.
10219
                $my_title = $xmldoc->createElement(
10220
                    'title',
10221
                    htmlspecialchars(
10222
                        api_utf8_encode($item->get_title()),
10223
                        ENT_QUOTES,
10224
                        'UTF-8'
10225
                    )
10226
                );
10227
                $my_item->appendChild($my_title);
10228
                // Give a child element <adlcp:prerequisites> to the <item> element.
10229
                $my_prereqs = $xmldoc->createElement(
10230
                    'adlcp:prerequisites',
10231
                    $this->get_scorm_prereq_string($my_item_id)
10232
                );
10233
                $my_prereqs->setAttribute('type', 'aicc_script');
10234
                $my_item->appendChild($my_prereqs);
10235
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10236
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10237
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10238
                //$xmldoc->createElement('adlcp:timelimitaction','');
10239
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10240
                //$xmldoc->createElement('adlcp:datafromlms','');
10241
                // Give a child element <adlcp:masteryscore> to the <item> element.
10242
                $my_masteryscore = $xmldoc->createElement(
10243
                    'adlcp:masteryscore',
10244
                    $item->get_mastery_score()
10245
                );
10246
                $my_item->appendChild($my_masteryscore);
10247
10248
                // Attach this item to the organization element or hits parent if there is one.
10249
                if (!empty($item->parent) && $item->parent != 0) {
10250
                    $children = $organization->childNodes;
10251
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10252
                    if (is_object($possible_parent)) {
10253
                        $possible_parent->appendChild($my_item);
10254
                    } else {
10255
                        if ($this->debug > 0) { error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found'); }
10256
                    }
10257
                } else {
10258
                    if ($this->debug > 0) { error_log('No parent'); }
10259
                    $organization->appendChild($my_item);
10260
                }
10261
10262
                // Get the path of the file(s) from the course directory root.
10263
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10264
10265
                if (!empty($path_to_remove)) {
10266
                    //From docs
10267
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $path_to_replace does not seem to be defined for all execution paths leading up to this point.
Loading history...
10268
10269
                    //From quiz
10270
                    if ($this->ref === 'chamilo_scorm_export') {
10271
                        $path_to_remove = 'scorm/'.$this->path.'/';
10272
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10273
                    }
10274
                } else {
10275
                    $my_xml_file_path = $my_file_path;
10276
                }
10277
10278
                $my_sub_dir = dirname($my_file_path);
10279
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10280
                $my_xml_sub_dir = $my_sub_dir;
10281
                // Give a <resource> child to the <resources> element
10282
                $my_resource = $xmldoc->createElement('resource');
10283
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10284
                $my_resource->setAttribute('type', 'webcontent');
10285
                $my_resource->setAttribute('href', $my_xml_file_path);
10286
                // adlcp:scormtype can be either 'sco' or 'asset'.
10287
                if ($item->type === 'sco') {
10288
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10289
                } else {
10290
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10291
                }
10292
                // xml:base is the base directory to find the files declared in this resource.
10293
                $my_resource->setAttribute('xml:base', '');
10294
                // Give a <file> child to the <resource> element.
10295
                $my_file = $xmldoc->createElement('file');
10296
                $my_file->setAttribute('href', $my_xml_file_path);
10297
                $my_resource->appendChild($my_file);
10298
10299
                // Dependency to other files - not yet supported.
10300
                $i = 1;
10301
                if ($inc_docs)
10302
                foreach ($inc_docs as $doc_info) {
10303
                    if (count($doc_info) < 1 || empty($doc_info[0])) {
10304
                        continue;
10305
                    }
10306
                    $my_dep = $xmldoc->createElement('resource');
10307
                    $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10308
                    $my_dep->setAttribute('identifier', $res_id);
10309
                    $my_dep->setAttribute('type', 'webcontent');
10310
                    $my_dep->setAttribute('adlcp:scormtype', 'asset');
10311
                    $my_dep_file = $xmldoc->createElement('file');
10312
                    // Check type of URL.
10313
                    if ($doc_info[1] == 'remote') {
10314
                        // Remote file. Save url as is.
10315
                        $my_dep_file->setAttribute('href', $doc_info[0]);
10316
                        $my_dep->setAttribute('xml:base', '');
10317
                    } elseif ($doc_info[1] === 'local') {
10318
                        switch ($doc_info[2]) {
10319
                            case 'url': // Local URL - save path as url for now, don't zip file.
10320
                                $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10321
                                $current_dir = dirname($abs_path);
10322
                                $current_dir = str_replace('\\', '/', $current_dir);
10323
                                $file_path = realpath($abs_path);
10324
                                $file_path = str_replace('\\', '/', $file_path);
10325
                                $my_dep_file->setAttribute('href', $file_path);
10326
                                $my_dep->setAttribute('xml:base', '');
10327
                                if (strstr($file_path, $main_path) !== false) {
10328
                                    // The calculated real path is really inside Chamilo's root path.
10329
                                    // Reduce file path to what's under the DocumentRoot.
10330
                                    $file_path = substr($file_path, strlen($root_path) - 1);
10331
                                    //echo $file_path;echo '<br /><br />';
10332
                                    //error_log(__LINE__.'Reduced url path: '.$file_path, 0);
10333
                                    $zip_files_abs[] = $file_path;
10334
                                    $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10335
                                    $my_dep_file->setAttribute('href', $file_path);
10336
                                    $my_dep->setAttribute('xml:base', '');
10337
                                } elseif (empty($file_path)) {
10338
                                    /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10339
                                    if (strpos($document_root, -1) == '/') {
10340
                                        $document_root = substr(0, -1, $document_root);
10341
                                    }*/
10342
                                    $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10343
                                    $file_path = str_replace('//', '/', $file_path);
10344
                                    if (file_exists($file_path)) {
10345
                                        $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10346
                                        $zip_files[] = $my_sub_dir.'/'.$file_path;
10347
                                        $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10348
                                        $my_dep_file->setAttribute('href', $file_path);
10349
                                        $my_dep->setAttribute('xml:base', '');
10350
                                    }
10351
                                }
10352
                                break;
10353
                            case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10354
                                $my_dep_file->setAttribute('href', $doc_info[0]);
10355
                                $my_dep->setAttribute('xml:base', '');
10356
10357
                                // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10358
                                // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10359
                                $abs_img_path_without_subdir = $doc_info[0];
10360
                                $relp = api_get_path(REL_PATH); // The url-append config param.
10361
                                $pos = strpos($abs_img_path_without_subdir, $relp);
10362
                                if ($pos === 0) {
10363
                                    $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp));
10364
                                }
10365
10366
                                //$file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir);
10367
                                $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10368
10369
                                $file_path = str_replace('\\', '/', $file_path);
10370
                                $file_path = str_replace('//', '/', $file_path);
10371
10372
                                // Prepare the current directory path (until just under 'document') with a trailing slash.
10373
                                $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10374
                                // Check if the current document is in that path.
10375
                                if (strstr($file_path, $cur_path) !== false) {
10376
                                    // The document is in that path, now get the relative path
10377
                                    // to the containing document.
10378
                                    $orig_file_path = dirname($cur_path.$my_file_path).'/';
10379
                                    $orig_file_path = str_replace('\\', '/', $orig_file_path);
10380
                                    $relative_path = '';
10381
                                    if (strstr($file_path, $cur_path) !== false) {
10382
                                        //$relative_path = substr($file_path, strlen($orig_file_path));
10383
                                        $relative_path = str_replace($cur_path, '', $file_path);
10384
                                        $file_path = substr($file_path, strlen($cur_path));
10385
                                    } else {
10386
                                        // This case is still a problem as it's difficult to calculate a relative path easily
10387
                                        // might still generate wrong links.
10388
                                        //$file_path = substr($file_path,strlen($cur_path));
10389
                                        // Calculate the directory path to the current file (without trailing slash).
10390
                                        $my_relative_path = dirname($file_path);
10391
                                        $my_relative_path = str_replace('\\', '/', $my_relative_path);
10392
                                        $my_relative_file = basename($file_path);
10393
                                        // Calculate the directory path to the containing file (without trailing slash).
10394
                                        $my_orig_file_path = substr($orig_file_path, 0, -1);
10395
                                        $dotdot = '';
10396
                                        $subdir = '';
10397
                                        while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) {
10398
                                            $my_relative_path2 = dirname($my_relative_path);
10399
                                            $my_relative_path2 = str_replace('\\', '/', $my_relative_path2);
10400
                                            $my_orig_file_path = dirname($my_orig_file_path);
10401
                                            $my_orig_file_path = str_replace('\\', '/', $my_orig_file_path);
10402
                                            $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir;
10403
                                            $dotdot += '../';
10404
                                            $my_relative_path = $my_relative_path2;
10405
                                        }
10406
                                        $relative_path = $dotdot.$subdir.$my_relative_file;
10407
                                    }
10408
                                    // Put the current document in the zip (this array is the array
10409
                                    // that will manage documents already in the course folder - relative).
10410
                                    $zip_files[] = $file_path;
10411
                                    // Update the links to the current document in the containing document (make them relative).
10412
                                    $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path);
10413
                                    $my_dep_file->setAttribute('href', $file_path);
10414
                                    $my_dep->setAttribute('xml:base', '');
10415
                                } elseif (strstr($file_path, $main_path) !== false) {
10416
                                    // The calculated real path is really inside Chamilo's root path.
10417
                                    // Reduce file path to what's under the DocumentRoot.
10418
                                    $file_path = substr($file_path, strlen($root_path));
10419
                                    //echo $file_path;echo '<br /><br />';
10420
                                    //error_log('Reduced path: '.$file_path, 0);
10421
                                    $zip_files_abs[] = $file_path;
10422
                                    $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10423
                                    $my_dep_file->setAttribute('href', 'document/'.$file_path);
10424
                                    $my_dep->setAttribute('xml:base', '');
10425
                                } elseif (empty($file_path)) {
10426
                                    /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10427
                                    if(strpos($document_root,-1) == '/') {
10428
                                        $document_root = substr(0, -1, $document_root);
10429
                                    }*/
10430
                                    $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
10431
                                    $file_path = str_replace('//', '/', $file_path);
10432
                                    $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10433
                                    $current_dir = dirname($abs_path);
10434
                                    $current_dir = str_replace('\\', '/', $current_dir);
10435
10436
                                    if (file_exists($file_path)) {
10437
                                        $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10438
                                        $zip_files[] = $my_sub_dir.'/'.$file_path;
10439
                                        $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10440
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10441
                                        $my_dep->setAttribute('xml:base', '');
10442
                                    }
10443
                                }
10444
                                break;
10445
                            case 'rel':
10446
                                // Path relative to the current document.
10447
                                // Save xml:base as current document's directory and save file in zip as subdir.file_path
10448
                                if (substr($doc_info[0], 0, 2) == '..') {
10449
                                    // Relative path going up.
10450
                                    $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10451
                                    $current_dir = str_replace('\\', '/', $current_dir);
10452
                                    $file_path = realpath($current_dir.$doc_info[0]);
10453
                                    $file_path = str_replace('\\', '/', $file_path);
10454
                                    if (strstr($file_path, $main_path) !== false) {
10455
                                        // The calculated real path is really inside Chamilo's root path.
10456
                                        // Reduce file path to what's under the DocumentRoot.
10457
                                        $file_path = substr($file_path, strlen($root_path));
10458
                                        $zip_files_abs[] = $file_path;
10459
                                        $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10460
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10461
                                        $my_dep->setAttribute('xml:base', '');
10462
                                    }
10463
                                } else {
10464
                                    $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10465
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10466
                                    $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10467
                                }
10468
                                break;
10469
                            default:
10470
                                $my_dep_file->setAttribute('href', $doc_info[0]);
10471
                                $my_dep->setAttribute('xml:base', '');
10472
                                break;
10473
                        }
10474
                    }
10475
                    $my_dep->appendChild($my_dep_file);
10476
                    $resources->appendChild($my_dep);
10477
                    $dependency = $xmldoc->createElement('dependency');
10478
                    $dependency->setAttribute('identifierref', $res_id);
10479
                    $my_resource->appendChild($dependency);
10480
                    $i++;
10481
                }
10482
                $resources->appendChild($my_resource);
10483
                $zip_files[] = $my_file_path;
10484
            } else {
10485
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10486
                switch ($item->type) {
10487
                    case TOOL_LINK:
10488
                        $my_item = $xmldoc->createElement('item');
10489
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10490
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10491
                        $my_item->setAttribute('isvisible', 'true');
10492
                        // Give a child element <title> to the <item> element.
10493
                        $my_title = $xmldoc->createElement(
10494
                            'title',
10495
                            htmlspecialchars(
10496
                                api_utf8_encode($item->get_title()),
10497
                                ENT_QUOTES,
10498
                                'UTF-8'
10499
                            )
10500
                        );
10501
                        $my_item->appendChild($my_title);
10502
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10503
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10504
                        $my_prereqs->setAttribute('type', 'aicc_script');
10505
                        $my_item->appendChild($my_prereqs);
10506
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10507
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10508
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10509
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10510
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10511
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10512
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10513
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10514
                        $my_item->appendChild($my_masteryscore);
10515
10516
                        // Attach this item to the organization element or its parent if there is one.
10517
                        if (!empty($item->parent) && $item->parent != 0) {
10518
                            $children = $organization->childNodes;
10519
                            for ($i = 0; $i < $children->length; $i++) {
10520
                                $item_temp = $children->item($i);
10521
                                if ($item_temp -> nodeName == 'item') {
10522
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10523
                                        $item_temp -> appendChild($my_item);
10524
                                    }
10525
                                }
10526
                            }
10527
                        } else {
10528
                            $organization->appendChild($my_item);
10529
                        }
10530
10531
                        $my_file_path = 'link_'.$item->get_id().'.html';
10532
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10533
                                WHERE c_id = '.$course_id.' AND id='.$item->path;
10534
                        $rs = Database::query($sql);
10535
                        if ($link = Database::fetch_array($rs)) {
10536
                            $url = $link['url'];
10537
                            $title = stripslashes($link['title']);
10538
                            $links_to_create[$my_file_path] = array('title' => $title, 'url' => $url);
10539
                            $my_xml_file_path = $my_file_path;
10540
                            $my_sub_dir = dirname($my_file_path);
10541
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10542
                            $my_xml_sub_dir = $my_sub_dir;
10543
                            // Give a <resource> child to the <resources> element.
10544
                            $my_resource = $xmldoc->createElement('resource');
10545
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10546
                            $my_resource->setAttribute('type', 'webcontent');
10547
                            $my_resource->setAttribute('href', $my_xml_file_path);
10548
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10549
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10550
                            // xml:base is the base directory to find the files declared in this resource.
10551
                            $my_resource->setAttribute('xml:base', '');
10552
                            // give a <file> child to the <resource> element.
10553
                            $my_file = $xmldoc->createElement('file');
10554
                            $my_file->setAttribute('href', $my_xml_file_path);
10555
                            $my_resource->appendChild($my_file);
10556
                            $resources->appendChild($my_resource);
10557
                        }
10558
                        break;
10559
                    case TOOL_QUIZ:
10560
                        $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard.
10561
                        $exe = new Exercise();
10562
                        $exe->read($exe_id);
10563
                        $my_item = $xmldoc->createElement('item');
10564
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10565
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10566
                        $my_item->setAttribute('isvisible', 'true');
10567
                        // Give a child element <title> to the <item> element.
10568
                        $my_title = $xmldoc->createElement(
10569
                            'title',
10570
                            htmlspecialchars(
10571
                                api_utf8_encode($item->get_title()),
10572
                                ENT_QUOTES,
10573
                                'UTF-8'
10574
                            )
10575
                        );
10576
                        $my_item->appendChild($my_title);
10577
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10578
                        //$my_item->appendChild($my_max_score);
10579
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10580
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10581
                        $my_prereqs->setAttribute('type', 'aicc_script');
10582
                        $my_item->appendChild($my_prereqs);
10583
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10584
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10585
                        $my_item->appendChild($my_masteryscore);
10586
10587
                        // Attach this item to the organization element or hits parent if there is one.
10588
10589
                        if (!empty($item->parent) && $item->parent != 0) {
10590
                            $children = $organization->childNodes;
10591
                            /*for ($i = 0; $i < $children->length; $i++) {
10592
                                $item_temp = $children->item($i);
10593
                                if ($exe_id == 81) {
10594
                                error_log($item_temp->nodeName );
10595
                                    error_log($item_temp->getAttribute('identifier'));
10596
                                }
10597
                                if ($item_temp->nodeName == 'item') {
10598
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10599
                                        $item_temp->appendChild($my_item);
10600
                                    }
10601
                                }
10602
                            }*/
10603
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10604
                            if ($possible_parent) {
10605
                                if ($possible_parent->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10606
                                    $possible_parent->appendChild($my_item);
10607
                                }
10608
                            }
10609
                        } else {
10610
                            $organization->appendChild($my_item);
10611
                        }
10612
10613
                        // Get the path of the file(s) from the course directory root
10614
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10615
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10616
                        // Write the contents of the exported exercise into a (big) html file
10617
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10618
                        $contents = ScormSection::export_exercise_to_scorm(
10619
                            $exe,
10620
                            true
10621
                        );
10622
10623
                        $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path;
10624
                        $res = file_put_contents($tmp_file_path, $contents);
10625
                        if ($res === false) {
10626
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10627
                        }
10628
                        $files_cleanup[] = $tmp_file_path;
10629
                        $my_xml_file_path = $my_file_path;
10630
                        $my_sub_dir = dirname($my_file_path);
10631
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10632
                        $my_xml_sub_dir = $my_sub_dir;
10633
                        // Give a <resource> child to the <resources> element.
10634
                        $my_resource = $xmldoc->createElement('resource');
10635
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10636
                        $my_resource->setAttribute('type', 'webcontent');
10637
                        $my_resource->setAttribute('href', $my_xml_file_path);
10638
10639
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10640
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10641
                        // xml:base is the base directory to find the files declared in this resource.
10642
                        $my_resource->setAttribute('xml:base', '');
10643
                        // Give a <file> child to the <resource> element.
10644
                        $my_file = $xmldoc->createElement('file');
10645
                        $my_file->setAttribute('href', $my_xml_file_path);
10646
                        $my_resource->appendChild($my_file);
10647
10648
                        // Get included docs.
10649
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
10650
                        // Dependency to other files - not yet supported.
10651
                        $i = 1;
10652
                        foreach ($inc_docs as $doc_info) {
10653
                            if (count($doc_info) < 1 || empty($doc_info[0])) { continue; }
10654
                            $my_dep = $xmldoc->createElement('resource');
10655
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10656
                            $my_dep->setAttribute('identifier', $res_id);
10657
                            $my_dep->setAttribute('type', 'webcontent');
10658
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
10659
                            $my_dep_file = $xmldoc->createElement('file');
10660
                            // Check type of URL.
10661
                            if ($doc_info[1] == 'remote') {
10662
                                // Remote file. Save url as is.
10663
                                $my_dep_file->setAttribute('href', $doc_info[0]);
10664
                                $my_dep->setAttribute('xml:base', '');
10665
                            } elseif ($doc_info[1] == 'local') {
10666
                                switch ($doc_info[2]) {
10667
                                    case 'url': // Local URL - save path as url for now, don't zip file.
10668
                                        // Save file but as local file (retrieve from URL).
10669
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10670
                                        $current_dir = dirname($abs_path);
10671
                                        $current_dir = str_replace('\\', '/', $current_dir);
10672
                                        $file_path = realpath($abs_path);
10673
                                        $file_path = str_replace('\\', '/', $file_path);
10674
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10675
                                        $my_dep->setAttribute('xml:base', '');
10676
                                        if (strstr($file_path, $main_path) !== false) {
10677
                                            // The calculated real path is really inside the chamilo root path.
10678
                                            // Reduce file path to what's under the DocumentRoot.
10679
                                            $file_path = substr($file_path, strlen($root_path));
10680
                                            $zip_files_abs[] = $file_path;
10681
                                            $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path);
10682
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10683
                                            $my_dep->setAttribute('xml:base', '');
10684
                                        } elseif (empty($file_path)) {
10685
                                            /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH)));
10686
                                            if (strpos($document_root,-1) == '/') {
10687
                                                $document_root = substr(0, -1, $document_root);
10688
                                            }*/
10689
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10690
                                            $file_path = str_replace('//', '/', $file_path);
10691
                                            if (file_exists($file_path)) {
10692
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10693
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
10694
                                                $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path);
10695
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10696
                                                $my_dep->setAttribute('xml:base', '');
10697
                                            }
10698
                                        }
10699
                                        break;
10700
                                    case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10701
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10702
                                        $current_dir = str_replace('\\', '/', $current_dir);
10703
                                        $file_path = realpath($doc_info[0]);
10704
                                        $file_path = str_replace('\\', '/', $file_path);
10705
                                        $my_dep_file->setAttribute('href', $file_path);
10706
                                        $my_dep->setAttribute('xml:base', '');
10707
10708
                                        if (strstr($file_path, $main_path) !== false) {
10709
                                            // The calculated real path is really inside the chamilo root path.
10710
                                            // Reduce file path to what's under the DocumentRoot.
10711
                                            $file_path = substr($file_path, strlen($root_path));
10712
                                            //echo $file_path;echo '<br /><br />';
10713
                                            //error_log('Reduced path: '.$file_path, 0);
10714
                                            $zip_files_abs[] = $file_path;
10715
                                            $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10716
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10717
                                            $my_dep->setAttribute('xml:base', '');
10718
                                        } elseif (empty($file_path)) {
10719
                                            /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10720
                                            if (strpos($document_root,-1) == '/') {
10721
                                                $document_root = substr(0, -1, $document_root);
10722
                                            }*/
10723
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
10724
                                            $file_path = str_replace('//', '/', $file_path);
10725
                                            if (file_exists($file_path)) {
10726
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10727
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
10728
                                                $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
10729
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10730
                                                $my_dep->setAttribute('xml:base', '');
10731
                                            }
10732
                                        }
10733
                                        break;
10734
                                    case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path
10735
                                        if (substr($doc_info[0], 0, 2) == '..') {
10736
                                            // Relative path going up.
10737
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10738
                                            $current_dir = str_replace('\\', '/', $current_dir);
10739
                                            $file_path = realpath($current_dir.$doc_info[0]);
10740
                                            $file_path = str_replace('\\', '/', $file_path);
10741
                                            //error_log($file_path.' <-> '.$main_path, 0);
10742
                                            if (strstr($file_path, $main_path) !== false) {
10743
                                                // The calculated real path is really inside Chamilo's root path.
10744
                                                // Reduce file path to what's under the DocumentRoot.
10745
10746
                                                $file_path = substr($file_path, strlen($root_path));
10747
                                                $file_path_dest = $file_path;
10748
10749
                                                // File path is courses/CHAMILO/document/....
10750
                                                $info_file_path = explode('/', $file_path);
10751
                                                if ($info_file_path[0] == 'courses') { // Add character "/" in file path.
10752
                                                    $file_path_dest = 'document/'.$file_path;
10753
                                                }
10754
10755
                                                //error_log('Reduced path: '.$file_path, 0);
10756
                                                $zip_files_abs[] = $file_path;
10757
10758
                                                $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest);
10759
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10760
                                                $my_dep->setAttribute('xml:base', '');
10761
                                            }
10762
                                        } else {
10763
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10764
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
10765
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10766
                                        }
10767
                                        break;
10768
                                    default:
10769
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
10770
                                        $my_dep->setAttribute('xml:base', '');
10771
                                        break;
10772
                                }
10773
                            }
10774
                            $my_dep->appendChild($my_dep_file);
10775
                            $resources->appendChild($my_dep);
10776
                            $dependency = $xmldoc->createElement('dependency');
10777
                            $dependency->setAttribute('identifierref', $res_id);
10778
                            $my_resource->appendChild($dependency);
10779
                            $i++;
10780
                        }
10781
                        $resources->appendChild($my_resource);
10782
                        $zip_files[] = $my_file_path;
10783
                        break;
10784
                    default:
10785
                        // Get the path of the file(s) from the course directory root
10786
                        $my_file_path = 'non_exportable.html';
10787
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
10788
                        $my_xml_file_path = $my_file_path;
10789
                        $my_sub_dir = dirname($my_file_path);
10790
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10791
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
10792
                        $my_xml_sub_dir = $my_sub_dir;
10793
                        // Give a <resource> child to the <resources> element.
10794
                        $my_resource = $xmldoc->createElement('resource');
10795
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10796
                        $my_resource->setAttribute('type', 'webcontent');
10797
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
10798
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10799
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
10800
                        // xml:base is the base directory to find the files declared in this resource.
10801
                        $my_resource->setAttribute('xml:base', '');
10802
                        // Give a <file> child to the <resource> element.
10803
                        $my_file = $xmldoc->createElement('file');
10804
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
10805
                        $my_resource->appendChild($my_file);
10806
                        $resources->appendChild($my_resource);
10807
10808
                        break;
10809
                }
10810
            }
10811
        }
10812
        $organizations->appendChild($organization);
10813
        $root->appendChild($organizations);
10814
        $root->appendChild($resources);
10815
        $xmldoc->appendChild($root);
10816
10817
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
10818
10819
        // TODO: Add a readme file here, with a short description and a link to the Reload player
10820
        // then add the file to the zip, then destroy the file (this is done automatically).
10821
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
10822
        foreach ($zip_files as $file_path) {
10823
            if (empty($file_path)) {
10824
                continue;
10825
            }
10826
10827
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
10828
            $dest_file = $archive_path.$temp_dir_short.'/'.$file_path;
10829
10830
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
10831
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
10832
            }
10833
            $this->create_path($dest_file);
10834
            @copy($filePath, $dest_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

10834
            /** @scrutinizer ignore-unhandled */ @copy($filePath, $dest_file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
10835
10836
            // Check if the file needs a link update.
10837
            if (in_array($file_path, array_keys($link_updates))) {
10838
                $string = file_get_contents($dest_file);
10839
                unlink($dest_file);
10840
                foreach ($link_updates[$file_path] as $old_new) {
10841
                    // This is an ugly hack that allows .flv files to be found by the flv player that
10842
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
10843
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
10844
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
10845
                    if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
10846
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
10847
                    } elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') {
10848
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
10849
                    }
10850
                    //Fix to avoid problems with default_course_document
10851
                    if (strpos("main/default_course_document", $old_new['dest'] === false)) {
10852
                        //$newDestination = str_replace('document/', $mult.'document/', $old_new['dest']);
10853
                        $newDestination = $old_new['dest'];
10854
                    } else {
10855
                        $newDestination = str_replace('document/', '', $old_new['dest']);
10856
                    }
10857
                    $string = str_replace($old_new['orig'], $newDestination, $string);
10858
10859
                    // Add files inside the HTMLs
10860
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
10861
                    $destinationFile = $archive_path.$temp_dir_short.'/'.$old_new['dest'];
10862
                    if (file_exists($sys_course_path.$new_path)) {
10863
                        copy($sys_course_path.$new_path, $destinationFile);
10864
                    }
10865
                }
10866
                file_put_contents($dest_file, $string);
10867
            }
10868
10869
            if (file_exists($filePath) && $copyAll) {
10870
                $extension = $this->get_extension($filePath);
10871
                if (in_array($extension, ['html', 'html'])) {
10872
                    $containerOrigin = dirname($filePath);
10873
                    $containerDestination = dirname($dest_file);
10874
10875
                    $finder = new Finder();
10876
                    $finder->files()->in($containerOrigin)
10877
                        ->notName('*_DELETED_*')
10878
                        ->exclude('share_folder')
10879
                        ->exclude('chat_files')
10880
                        ->exclude('certificates')
10881
                    ;
10882
10883
                    if (is_dir($containerOrigin) &&
10884
                        is_dir($containerDestination)
10885
                    ) {
10886
                        $fs = new Filesystem();
10887
                        $fs->mirror(
10888
                            $containerOrigin,
10889
                            $containerDestination,
10890
                            $finder
10891
                        );
10892
                    }
10893
                }
10894
            }
10895
        }
10896
10897
        foreach ($zip_files_abs as $file_path) {
10898
            if (empty($file_path)) {
10899
                continue;
10900
            }
10901
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
10902
                continue;
10903
            }
10904
10905
            $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path;
10906
            $this->create_path($dest_file);
10907
            copy($main_path.$file_path, $dest_file);
10908
            // Check if the file needs a link update.
10909
            if (in_array($file_path, array_keys($link_updates))) {
10910
                $string = file_get_contents($dest_file);
10911
                unlink($dest_file);
10912
                foreach ($link_updates[$file_path] as $old_new) {
10913
                    // This is an ugly hack that allows .flv files to be found by the flv player that
10914
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
10915
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
10916
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
10917
                    if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
10918
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
10919
                    }
10920
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
10921
                }
10922
                file_put_contents($dest_file, $string);
10923
            }
10924
        }
10925
10926
        if (is_array($links_to_create)) {
10927
            foreach ($links_to_create as $file => $link) {
10928
                $file_content = '<!DOCTYPE html><head>
10929
                                <meta charset="'.api_get_language_isocode().'" />
10930
                                <title>'.$link['title'].'</title>
10931
                                </head>
10932
                                <body dir="'.api_get_text_direction().'">
10933
                                <div style="text-align:center">
10934
                                <a href="'.$link['url'].'">'.$link['title'].'</a></div>
10935
                                </body>
10936
                                </html>';
10937
                file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content);
10938
            }
10939
        }
10940
10941
        // Add non exportable message explanation.
10942
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
10943
        $file_content = '<!DOCTYPE html><head>
10944
                        <meta charset="'.api_get_language_isocode().'" />
10945
                        <title>'.$lang_not_exportable.'</title>
10946
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
10947
                        </head>
10948
                        <body dir="'.api_get_text_direction().'">';
10949
        $file_content .=
10950
            <<<EOD
10951
                    <style>
10952
            .error-message {
10953
                font-family: arial, verdana, helvetica, sans-serif;
10954
                border-width: 1px;
10955
                border-style: solid;
10956
                left: 50%;
10957
                margin: 10px auto;
10958
                min-height: 30px;
10959
                padding: 5px;
10960
                right: 50%;
10961
                width: 500px;
10962
                background-color: #FFD1D1;
10963
                border-color: #FF0000;
10964
                color: #000;
10965
            }
10966
        </style>
10967
    <body>
10968
        <div class="error-message">
10969
            $lang_not_exportable
10970
        </div>
10971
    </body>
10972
</html>
10973
EOD;
10974
        if (!is_dir($archive_path.$temp_dir_short.'/document')) {
10975
            @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

10975
            /** @scrutinizer ignore-unhandled */ @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
10976
        }
10977
        file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content);
10978
10979
        // Add the extra files that go along with a SCORM package.
10980
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
10981
10982
        $fs = new Filesystem();
10983
        $fs->mirror($main_code_path, $archive_path.$temp_dir_short);
10984
10985
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
10986
        $manifest = @$xmldoc->saveXML();
10987
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
10988
        file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
10989
        $zip_folder->add(
10990
            $archive_path.'/'.$temp_dir_short,
10991
            PCLZIP_OPT_REMOVE_PATH,
10992
            $archive_path.'/'.$temp_dir_short.'/'
10993
        );
10994
10995
        // Clean possible temporary files.
10996
        foreach ($files_cleanup as $file) {
10997
            $res = unlink($file);
10998
            if ($res === false) {
10999
                error_log(
11000
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11001
                    0
11002
                );
11003
            }
11004
        }
11005
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11006
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11007
    }
11008
11009
    /**
11010
     * @param int $lp_id
11011
     * @return bool
11012
     */
11013
    public function scorm_export_to_pdf($lp_id)
11014
    {
11015
        $lp_id = intval($lp_id);
11016
        $files_to_export = [];
11017
        $course_data = api_get_course_info($this->cc);
11018
        if (!empty($course_data)) {
11019
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11020
            $list = self::get_flat_ordered_items_list($lp_id);
11021
            if (!empty($list)) {
11022
                foreach ($list as $item_id) {
11023
                    $item = $this->items[$item_id];
11024
                    switch ($item->type) {
11025
                        case 'document':
11026
                            //Getting documents from a LP with chamilo documents
11027
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11028
                            // Try loading document from the base course.
11029
                            if (empty($file_data) && !empty($sessionId)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionId seems to never exist and therefore empty should always be true.
Loading history...
11030
                                $file_data = DocumentManager::get_document_data_by_id(
11031
                                    $item->path,
11032
                                    $this->cc,
11033
                                    false,
11034
                                    0
11035
                                );
11036
                            }
11037
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11038
                            if (file_exists($file_path)) {
11039
                                $files_to_export[] = array(
11040
                                    'title' => $item->get_title(),
11041
                                    'path' => $file_path
11042
                                );
11043
                            }
11044
                            break;
11045
                        case 'asset': //commes from a scorm package generated by chamilo
11046
                        case 'sco':
11047
                            $file_path = $scorm_path.'/'.$item->path;
11048
                            if (file_exists($file_path)) {
11049
                                $files_to_export[] = array(
11050
                                    'title' => $item->get_title(),
11051
                                    'path' => $file_path
11052
                                );
11053
                            }
11054
                            break;
11055
                        case 'dir':
11056
                            $files_to_export[] = array(
11057
                                'title' => $item->get_title(),
11058
                                'path' => null
11059
                            );
11060
                            break;
11061
                    }
11062
                }
11063
            }
11064
            $pdf = new PDF();
11065
            $result = $pdf->html_to_pdf(
11066
                $files_to_export,
11067
                $this->name,
11068
                $this->cc,
11069
                true
11070
            );
11071
            return $result;
11072
        }
11073
11074
        return false;
11075
    }
11076
11077
    /**
11078
     * Temp function to be moved in main_api or the best place around for this.
11079
     * Creates a file path if it doesn't exist
11080
     * @param string $path
11081
     */
11082
    public function create_path($path)
11083
    {
11084
        $path_bits = explode('/', dirname($path));
11085
11086
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11087
        $path_built = IS_WINDOWS_OS ? '' : '/';
11088
        foreach ($path_bits as $bit) {
11089
            if (!empty ($bit)) {
11090
                $new_path = $path_built.$bit;
11091
                if (is_dir($new_path)) {
11092
                    $path_built = $new_path.'/';
11093
                } else {
11094
                    mkdir($new_path, api_get_permissions_for_new_directories());
11095
                    $path_built = $new_path.'/';
11096
                }
11097
            }
11098
        }
11099
    }
11100
11101
    /**
11102
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11103
     * @return	boolean	The results of the unlink function, or false if there was no image to start with
11104
     */
11105
    public function delete_lp_image()
11106
    {
11107
        $img = $this->get_preview_image();
11108
        if ($img != '') {
11109
            $del_file = $this->get_preview_image_path(null, 'sys');
11110
            if (isset($del_file) && file_exists($del_file)) {
11111
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11112
                if (file_exists($del_file_2)) {
11113
                    unlink($del_file_2);
11114
                }
11115
                $this->set_preview_image('');
11116
                return @unlink($del_file);
11117
            }
11118
        }
11119
        return false;
11120
    }
11121
11122
    /**
11123
     * Uploads an author image to the upload/learning_path/images directory
11124
     * @param	array	The image array, coming from the $_FILES superglobal
11125
     * @return	boolean	True on success, false on error
11126
     */
11127
    public function upload_image($image_array)
11128
    {
11129
        if (!empty($image_array['name'])) {
11130
            $upload_ok = process_uploaded_file($image_array);
11131
            $has_attachment = true;
11132
        }
11133
11134
        if ($upload_ok && $has_attachment) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $has_attachment does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $upload_ok does not seem to be defined for all execution paths leading up to this point.
Loading history...
11135
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11136
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11137
            $updir = $sys_course_path.$courseDir;
11138
            // Try to add an extension to the file if it hasn't one.
11139
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11140
11141
            if (filter_extension($new_file_name)) {
11142
                $file_extension = explode('.', $image_array['name']);
11143
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
0 ignored issues
show
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

11143
                $file_extension = strtolower($file_extension[/** @scrutinizer ignore-call */ sizeof($file_extension) - 1]);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
11144
                $filename = uniqid('');
11145
                $new_file_name = $filename.'.'.$file_extension;
11146
                $new_path = $updir.'/'.$new_file_name;
11147
11148
                // Resize the image.
11149
                $temp = new Image($image_array['tmp_name']);
11150
                $temp->resize(104);
11151
                $result = $temp->send_image($new_path);
11152
11153
                // Storing the image filename.
11154
                if ($result) {
11155
                    $this->set_preview_image($new_file_name);
11156
11157
                    //Resize to 64px to use on course homepage
11158
                    $temp->resize(64);
11159
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11160
                    return true;
11161
                }
11162
            }
11163
        }
11164
11165
        return false;
11166
    }
11167
11168
    /**
11169
     * @param int $lp_id
11170
     * @param string $status
11171
     */
11172
    public function set_autolaunch($lp_id, $status)
11173
    {
11174
        $course_id = api_get_course_int_id();
11175
        $lp_id = intval($lp_id);
11176
        $status = intval($status);
11177
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11178
11179
        // Setting everything to autolaunch = 0
11180
        $attributes['autolaunch'] = 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
11181
        $where = array(
11182
            'session_id = ? AND c_id = ? ' => array(
11183
                api_get_session_id(),
11184
                $course_id
11185
            )
11186
        );
11187
        Database::update($lp_table, $attributes, $where);
11188
        if ($status == 1) {
11189
            //Setting my lp_id to autolaunch = 1
11190
            $attributes['autolaunch'] = 1;
11191
            $where = array(
11192
                'iid = ? AND session_id = ? AND c_id = ?' => array(
11193
                    $lp_id,
11194
                    api_get_session_id(),
11195
                    $course_id
11196
                )
11197
            );
11198
            Database::update($lp_table, $attributes, $where);
11199
        }
11200
    }
11201
11202
    /**
11203
     * Gets previous_item_id for the next element of the lp_item table
11204
     * @author Isaac flores paz
11205
     * @return	integer	Previous item ID
11206
     */
11207
    public function select_previous_item_id()
11208
    {
11209
        $course_id = api_get_course_int_id();
11210
        if ($this->debug > 0) {
11211
            error_log('New LP - In learnpath::select_previous_item_id()', 0);
11212
        }
11213
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11214
11215
        // Get the max order of the items
11216
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11217
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11218
        $rs_max_order = Database::query($sql);
11219
        $row_max_order = Database::fetch_object($rs_max_order);
11220
        $max_order = $row_max_order->display_order;
11221
        // Get the previous item ID
11222
        $sql = "SELECT iid as previous FROM $table_lp_item
11223
                WHERE 
11224
                    c_id = $course_id AND 
11225
                    lp_id = ".$this->lp_id." AND 
11226
                    display_order = '$max_order' ";
11227
        $rs_max = Database::query($sql);
11228
        $row_max = Database::fetch_object($rs_max);
11229
11230
        // Return the previous item ID
11231
        return $row_max->previous;
11232
    }
11233
11234
    /**
11235
     * Copies an LP
11236
     */
11237
    public function copy()
11238
    {
11239
        // Course builder
11240
        $cb = new CourseBuilder();
11241
11242
        //Setting tools that will be copied
11243
        $cb->set_tools_to_build(array('learnpaths'));
11244
11245
        //Setting elements that will be copied
11246
        $cb->set_tools_specific_id_list(
11247
            array('learnpaths' => array($this->lp_id))
11248
        );
11249
11250
        $course = $cb->build();
11251
11252
        //Course restorer
11253
        $course_restorer = new CourseRestorer($course);
11254
        $course_restorer->set_add_text_in_items(true);
11255
        $course_restorer->set_tool_copy_settings(
11256
            array('learnpaths' => array('reset_dates' => true))
11257
        );
11258
        $course_restorer->restore(
11259
            api_get_course_id(),
11260
            api_get_session_id(),
11261
            false,
11262
            false
11263
        );
11264
    }
11265
11266
    /**
11267
     * Verify document size
11268
     * @param string $s
11269
     * @return bool
11270
     */
11271
    public static function verify_document_size($s)
11272
    {
11273
        $post_max = ini_get('post_max_size');
11274
        if (substr($post_max, -1, 1) == 'M') {
11275
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11276
        } elseif (substr($post_max, -1, 1) == 'G') {
11277
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11278
        }
11279
        $upl_max = ini_get('upload_max_filesize');
11280
        if (substr($upl_max, -1, 1) == 'M') {
11281
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11282
        } elseif (substr($upl_max, -1, 1) == 'G') {
11283
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11284
        }
11285
        $documents_total_space = DocumentManager::documents_total_space();
11286
        $course_max_space = DocumentManager::get_course_quota();
11287
        $total_size = filesize($s) + $documents_total_space;
11288
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11289
            return true;
11290
        } else {
11291
            return false;
11292
        }
11293
    }
11294
11295
    /**
11296
     * Clear LP prerequisites
11297
     */
11298
    public function clear_prerequisites()
11299
    {
11300
        $course_id = $this->get_course_int_id();
11301
        if ($this->debug > 0) {
11302
            error_log('New LP - In learnpath::clear_prerequisites()', 0);
11303
        }
11304
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11305
        $lp_id = $this->get_id();
11306
        //Cleaning prerequisites
11307
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11308
                WHERE c_id = $course_id AND lp_id = $lp_id";
11309
        Database::query($sql);
11310
11311
        //Cleaning mastery score for exercises
11312
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11313
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11314
        Database::query($sql);
11315
    }
11316
11317
    public function set_previous_step_as_prerequisite_for_all_items()
11318
    {
11319
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11320
        $course_id = $this->get_course_int_id();
11321
        $lp_id = $this->get_id();
11322
11323
        if (!empty($this->items)) {
11324
            $previous_item_id = null;
11325
            $previous_item_max = 0;
11326
            $previous_item_type = null;
11327
            $last_item_not_dir = null;
11328
            $last_item_not_dir_type = null;
11329
            $last_item_not_dir_max = null;
11330
11331
            foreach ($this->ordered_items as $itemId) {
11332
                $item = $this->getItem($itemId);
11333
                // if there was a previous item... (otherwise jump to set it)
11334
                if (!empty($previous_item_id)) {
11335
                    $current_item_id = $item->get_id(); //save current id
11336
                    if ($item->get_type() != 'dir') {
11337
                        // Current item is not a folder, so it qualifies to get a prerequisites
11338
                        if ($last_item_not_dir_type == 'quiz') {
11339
                            // if previous is quiz, mark its max score as default score to be achieved
11340
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11341
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11342
                            Database::query($sql);
11343
                        }
11344
                        // now simply update the prerequisite to set it to the last non-chapter item
11345
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11346
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11347
                        Database::query($sql);
11348
                        // record item as 'non-chapter' reference
11349
                        $last_item_not_dir = $item->get_id();
11350
                        $last_item_not_dir_type = $item->get_type();
11351
                        $last_item_not_dir_max = $item->get_max();
11352
                    }
11353
                } else {
11354
                    if ($item->get_type() != 'dir') {
11355
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11356
                        $last_item_not_dir = $item->get_id();
11357
                        $last_item_not_dir_type = $item->get_type();
11358
                        $last_item_not_dir_max = $item->get_max();
11359
                    }
11360
                }
11361
                // Saving the item as "previous item" for the next loop
11362
                $previous_item_id = $item->get_id();
11363
                $previous_item_max = $item->get_max();
11364
                $previous_item_type = $item->get_type();
11365
            }
11366
        }
11367
    }
11368
11369
    /**
11370
     * @param array $params
11371
     */
11372
    public static function createCategory($params)
11373
    {
11374
        $em = Database::getManager();
11375
        $item = new CLpCategory();
11376
        $item->setName($params['name']);
11377
        $item->setCId($params['c_id']);
11378
        $em->persist($item);
11379
        $em->flush();
11380
    }
11381
    /**
11382
     * @param array $params
11383
     */
11384
    public static function updateCategory($params)
11385
    {
11386
        $em = Database::getManager();
11387
        /** @var CLpCategory $item */
11388
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11389
        if ($item) {
11390
            $item->setName($params['name']);
11391
            $em->merge($item);
11392
            $em->flush();
11393
        }
11394
    }
11395
11396
    /**
11397
     * @param int $id
11398
     */
11399
    public static function moveUpCategory($id)
11400
    {
11401
        $id = (int) $id;
11402
        $em = Database::getManager();
11403
        /** @var CLpCategory $item */
11404
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11405
        if ($item) {
11406
            $position = $item->getPosition() - 1;
11407
            $item->setPosition($position);
11408
            $em->persist($item);
11409
            $em->flush();
11410
        }
11411
    }
11412
11413
    /**
11414
     * @param int $id
11415
     */
11416
    public static function moveDownCategory($id)
11417
    {
11418
        $id = (int) $id;
11419
        $em = Database::getManager();
11420
        /** @var CLpCategory $item */
11421
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11422
        if ($item) {
11423
            $position = $item->getPosition() + 1;
11424
            $item->setPosition($position);
11425
            $em->persist($item);
11426
            $em->flush();
11427
        }
11428
    }
11429
11430
    /**
11431
     * @param int $courseId
11432
     * @return int|mixed
11433
     */
11434
    public static function getCountCategories($courseId)
11435
    {
11436
        if (empty($course_id)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $course_id does not exist. Did you maybe mean $courseId?
Loading history...
11437
            return 0;
11438
        }
11439
        $em = Database::getManager();
11440
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11441
        $query->setParameter('id', $courseId);
11442
11443
        return $query->getSingleScalarResult();
11444
    }
11445
11446
    /**
11447
     * @param int $courseId
11448
     *
11449
     * @return mixed
11450
     */
11451
    public static function getCategories($courseId)
11452
    {
11453
        $em = Database::getManager();
11454
        //Default behaviour
11455
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11456
            array('cId' => $course_id),
11457
            array('name' => 'ASC')
11458
        );*/
11459
11460
        // Using doctrine extensions
11461
        /** @var SortableRepository $repo */
11462
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11463
        $items = $repo
11464
            ->getBySortableGroupsQuery(['cId' => $courseId])
11465
            ->getResult();
11466
11467
        return $items;
11468
    }
11469
11470
    /**
11471
     * @param int $id
11472
     *
11473
     * @return CLpCategory
11474
     */
11475
    public static function getCategory($id)
11476
    {
11477
        $id = (int) $id;
11478
        $em = Database::getManager();
11479
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11480
11481
        return $item;
11482
    }
11483
11484
    /**
11485
     * @param int $courseId
11486
     * @return array
11487
     */
11488
    public static function getCategoryByCourse($courseId)
11489
    {
11490
        $em = Database::getManager();
11491
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11492
            array('cId' => $courseId)
11493
        );
11494
11495
        return $items;
11496
    }
11497
11498
    /**
11499
     * @param int $id
11500
     *
11501
     * @return mixed
11502
     */
11503
    public static function deleteCategory($id)
11504
    {
11505
        $em = Database::getManager();
11506
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11507
        if ($item) {
11508
            $courseId = $item->getCId();
11509
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11510
            $query->setParameter('id', $courseId);
11511
            $query->setParameter('catId', $item->getId());
11512
            $lps = $query->getResult();
11513
11514
            // Setting category = 0.
11515
            if ($lps) {
11516
                foreach ($lps as $lpItem) {
11517
                    $lpItem->setCategoryId(0);
11518
                }
11519
            }
11520
11521
            // Removing category.
11522
            $em->remove($item);
11523
            $em->flush();
11524
11525
            $courseInfo = api_get_course_info_by_id($courseId);
11526
            $sessionId = api_get_session_id();
11527
11528
            // Delete link tool
11529
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11530
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11531
            // Delete tools
11532
            $sql = "DELETE FROM $tbl_tool
11533
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11534
            Database::query($sql);
11535
        }
11536
    }
11537
11538
    /**
11539
     * @param int $courseId
11540
     * @param bool $addSelectOption
11541
     *
11542
     * @return mixed
11543
     */
11544
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
11545
    {
11546
        $items = self::getCategoryByCourse($courseId);
11547
        $cats = [];
11548
        if ($addSelectOption) {
11549
            $cats = array(get_lang('SelectACategory'));
11550
        }
11551
11552
        if (!empty($items)) {
11553
            foreach ($items as $cat) {
11554
                $cats[$cat->getId()] = $cat->getName();
11555
            }
11556
        }
11557
11558
        return $cats;
11559
    }
11560
11561
    /**
11562
     * Return the scorm item type object with spaces replaced with _
11563
     * The return result is use to build a css classname like scorm_type_$return
11564
     * @param $in_type
11565
     * @return mixed
11566
     */
11567
    private static function format_scorm_type_item($in_type)
11568
    {
11569
        return str_replace(' ', '_', $in_type);
11570
    }
11571
11572
    /**
11573
     * @param string $courseCode
11574
     * @param int $lp_id
11575
     * @param int $user_id
11576
     *
11577
     * @return learnpath
11578
     */
11579
    public static function getLpFromSession($courseCode, $lp_id, $user_id)
11580
    {
11581
        $learnPath = null;
11582
        $lpObject = Session::read('lpobject');
11583
        if ($lpObject !== null) {
11584
            $learnPath = unserialize($lpObject);
11585
        }
11586
11587
        if (!is_object($learnPath)) {
11588
            $learnPath = new learnpath($courseCode, $lp_id, $user_id);
11589
        }
11590
11591
        return $learnPath;
11592
    }
11593
11594
    /**
11595
     * @param int $itemId
11596
     * @return learnpathItem|false
11597
     */
11598
    public function getItem($itemId)
11599
    {
11600
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
11601
            return $this->items[$itemId];
11602
        }
11603
11604
        return false;
11605
    }
11606
11607
    /**
11608
     * @return int
11609
     */
11610
    public function getCategoryId()
11611
    {
11612
        return $this->categoryId;
11613
    }
11614
11615
    /**
11616
     * @param int $categoryId
11617
     * @return bool
11618
     */
11619
    public function setCategoryId($categoryId)
11620
    {
11621
        $this->categoryId = intval($categoryId);
11622
        $courseId = api_get_course_int_id();
11623
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11624
        $lp_id = $this->get_id();
11625
        $sql = "UPDATE $lp_table SET category_id = ".$this->categoryId."
11626
                WHERE iid = $lp_id";
11627
        Database::query($sql);
11628
11629
        return true;
11630
    }
11631
11632
    /**
11633
     * Get whether this is a learning path with the possibility to subscribe
11634
     * users or not
11635
     * @return int
11636
     */
11637
    public function getSubscribeUsers()
11638
    {
11639
        return $this->subscribeUsers;
11640
    }
11641
11642
    /**
11643
     * Set whether this is a learning path with the possibility to subscribe
11644
     * users or not
11645
     * @param int $value (0 = false, 1 = true)
11646
     * @return bool
11647
     */
11648
    public function setSubscribeUsers($value)
11649
    {
11650
        if ($this->debug > 0) {
11651
            error_log('New LP - In learnpath::set_subscribe_users()', 0);
11652
        }
11653
        $this->subscribeUsers = (int) $value;
11654
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11655
        $lp_id = $this->get_id();
11656
        $sql = "UPDATE $lp_table SET subscribe_users = ".$this->subscribeUsers."
11657
                WHERE iid = $lp_id";
11658
        Database::query($sql);
11659
11660
        return true;
11661
    }
11662
11663
    /**
11664
     * Calculate the count of stars for a user in this LP
11665
     * This calculation is based on the following rules:
11666
     * - the student gets one star when he gets to 50% of the learning path
11667
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
11668
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
11669
     * - the student gets the final star when the score for the *last* test is >= 80%
11670
     * @param int $sessionId Optional. The session ID
11671
     * @return int The count of stars
11672
     */
11673
    public function getCalculateStars($sessionId = 0)
11674
    {
11675
        $stars = 0;
11676
        $progress = self::getProgress(
11677
            $this->lp_id,
11678
            $this->user_id,
11679
            $this->course_int_id,
11680
            $sessionId
11681
        );
11682
11683
        if ($progress >= 50) {
11684
            $stars++;
11685
        }
11686
11687
        // Calculate stars chapters evaluation
11688
        $exercisesItems = $this->getExercisesItems();
11689
11690
        if (!empty($exercisesItems)) {
11691
            $totalResult = 0;
11692
11693
            foreach ($exercisesItems as $exerciseItem) {
11694
                $exerciseResultInfo = Event::getExerciseResultsByUser(
11695
                    $this->user_id,
11696
                    $exerciseItem->path,
11697
                    $this->course_int_id,
11698
                    $sessionId,
11699
                    $this->lp_id,
11700
                    $exerciseItem->db_id
11701
                );
11702
11703
                $exerciseResultInfo = end($exerciseResultInfo);
11704
11705
                if (!$exerciseResultInfo) {
11706
                    continue;
11707
                }
11708
11709
                if (!empty($exerciseResultInfo['exe_weighting'])) {
11710
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
11711
                } else {
11712
                    $exerciseResult = 0;
11713
                }
11714
                $totalResult += $exerciseResult;
11715
            }
11716
11717
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
11718
11719
            if ($totalExerciseAverage >= 50) {
11720
                $stars++;
11721
            }
11722
11723
            if ($totalExerciseAverage >= 80) {
11724
                $stars++;
11725
            }
11726
        }
11727
11728
        // Calculate star for final evaluation
11729
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11730
11731
        if (!empty($finalEvaluationItem)) {
11732
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11733
                $this->user_id,
11734
                $finalEvaluationItem->path,
11735
                $this->course_int_id,
11736
                $sessionId,
11737
                $this->lp_id,
11738
                $finalEvaluationItem->db_id
11739
            );
11740
11741
            $evaluationResultInfo = end($evaluationResultInfo);
11742
11743
            if ($evaluationResultInfo) {
11744
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
11745
11746
                if ($evaluationResult >= 80) {
11747
                    $stars++;
11748
                }
11749
            }
11750
        }
11751
11752
        return $stars;
11753
    }
11754
11755
    /**
11756
     * Get the items of exercise type
11757
     * @return array The items. Otherwise return false
11758
     */
11759
    public function getExercisesItems()
11760
    {
11761
        $exercises = [];
11762
        foreach ($this->items as $item) {
11763
            if ($item->type != 'quiz') {
11764
                continue;
11765
            }
11766
            $exercises[] = $item;
11767
        }
11768
11769
        array_pop($exercises);
11770
11771
        return $exercises;
11772
    }
11773
11774
    /**
11775
     * Get the item of exercise type (evaluation type)
11776
     * @return array The final evaluation. Otherwise return false
11777
     */
11778
    public function getFinalEvaluationItem()
11779
    {
11780
        $exercises = [];
11781
        foreach ($this->items as $item) {
11782
            if ($item->type != 'quiz') {
11783
                continue;
11784
            }
11785
11786
            $exercises[] = $item;
11787
        }
11788
11789
        return array_pop($exercises);
11790
    }
11791
11792
    /**
11793
     * Calculate the total points achieved for the current user in this learning path
11794
     * @param int $sessionId Optional. The session Id
11795
     * @return int
11796
     */
11797
    public function getCalculateScore($sessionId = 0)
11798
    {
11799
        // Calculate stars chapters evaluation
11800
        $exercisesItems = $this->getExercisesItems();
11801
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11802
        $totalExercisesResult = 0;
11803
        $totalEvaluationResult = 0;
11804
11805
        if ($exercisesItems !== false) {
11806
            foreach ($exercisesItems as $exerciseItem) {
11807
                $exerciseResultInfo = Event::getExerciseResultsByUser(
11808
                    $this->user_id,
11809
                    $exerciseItem->path,
11810
                    $this->course_int_id,
11811
                    $sessionId,
11812
                    $this->lp_id,
11813
                    $exerciseItem->db_id
11814
                );
11815
11816
                $exerciseResultInfo = end($exerciseResultInfo);
11817
11818
                if (!$exerciseResultInfo) {
11819
                    continue;
11820
                }
11821
11822
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
11823
            }
11824
        }
11825
11826
        if (!empty($finalEvaluationItem)) {
11827
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11828
                $this->user_id,
11829
                $finalEvaluationItem->path,
11830
                $this->course_int_id,
11831
                $sessionId,
11832
                $this->lp_id,
11833
                $finalEvaluationItem->db_id
11834
            );
11835
11836
            $evaluationResultInfo = end($evaluationResultInfo);
11837
11838
            if ($evaluationResultInfo) {
11839
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
11840
            }
11841
        }
11842
11843
        return $totalExercisesResult + $totalEvaluationResult;
11844
    }
11845
11846
    /**
11847
     * Check if URL is not allowed to be show in a iframe
11848
     * @param string $src
11849
     *
11850
     * @return string
11851
     */
11852
    public function fixBlockedLinks($src)
11853
    {
11854
        $urlInfo = parse_url($src);
11855
        $platformProtocol = 'https';
11856
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
11857
            $platformProtocol = 'http';
11858
        }
11859
11860
        $protocolFixApplied = false;
11861
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
11862
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
11863
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
11864
11865
        if ($platformProtocol != $scheme) {
11866
            Session::write('x_frame_source', $src);
11867
            $src = 'blank.php?error=x_frames_options';
11868
            $protocolFixApplied = true;
11869
        }
11870
11871
        if ($protocolFixApplied == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
11872
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
11873
                // Check X-Frame-Options
11874
                $ch = curl_init();
11875
11876
                $options = array(
11877
                    CURLOPT_URL => $src,
11878
                    CURLOPT_RETURNTRANSFER => true,
11879
                    CURLOPT_HEADER => true,
11880
                    CURLOPT_FOLLOWLOCATION => true,
11881
                    CURLOPT_ENCODING => "",
11882
                    CURLOPT_AUTOREFERER => true,
11883
                    CURLOPT_CONNECTTIMEOUT => 120,
11884
                    CURLOPT_TIMEOUT => 120,
11885
                    CURLOPT_MAXREDIRS => 10,
11886
                );
11887
                curl_setopt_array($ch, $options);
11888
                $response = curl_exec($ch);
11889
                $httpCode = curl_getinfo($ch);
11890
                $headers = substr($response, 0, $httpCode['header_size']);
11891
11892
                $error = false;
11893
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
11894
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
11895
                ) {
11896
                    $error = true;
11897
                }
11898
11899
                if ($error) {
11900
                    Session::write('x_frame_source', $src);
11901
                    $src = 'blank.php?error=x_frames_options';
11902
                }
11903
            }
11904
        }
11905
11906
        return $src;
11907
    }
11908
11909
    /**
11910
     * Check if this LP has a created forum in the basis course
11911
     * @return boolean
11912
     */
11913
    public function lpHasForum()
11914
    {
11915
        $forumTable = Database::get_course_table(TABLE_FORUM);
11916
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
11917
11918
        $fakeFrom = "
11919
            $forumTable f
11920
            INNER JOIN $itemProperty ip
11921
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
11922
        ";
11923
11924
        $resultData = Database::select(
11925
            'COUNT(f.iid) AS qty',
11926
            $fakeFrom,
11927
            [
11928
                'where' => [
11929
                    'ip.visibility != ? AND ' => 2,
11930
                    'ip.tool = ? AND ' => TOOL_FORUM,
11931
                    'f.c_id = ? AND ' => intval($this->course_int_id),
11932
                    'f.lp_id = ?' => intval($this->lp_id)
11933
                ]
11934
            ],
11935
            'first'
11936
        );
11937
11938
        return $resultData['qty'] > 0;
11939
    }
11940
11941
    /**
11942
     * Get the forum for this learning path
11943
     * @param int $sessionId
11944
     * @return boolean
11945
     */
11946
    public function getForum($sessionId = 0)
11947
    {
11948
        $forumTable = Database::get_course_table(TABLE_FORUM);
11949
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
11950
11951
        $fakeFrom = "$forumTable f
11952
            INNER JOIN $itemProperty ip ";
11953
11954
        if ($this->lp_session_id == 0) {
11955
            $fakeFrom .= "
11956
                ON (
11957
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
11958
                        f.session_id = ip.session_id OR ip.session_id IS NULL
11959
                    )
11960
                )
11961
            ";
11962
        } else {
11963
            $fakeFrom .= "
11964
                ON (
11965
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
11966
                )
11967
            ";
11968
        }
11969
11970
        $resultData = Database::select(
11971
            'f.*',
11972
            $fakeFrom,
11973
            [
11974
                'where' => [
11975
                    'ip.visibility != ? AND ' => 2,
11976
                    'ip.tool = ? AND ' => TOOL_FORUM,
11977
                    'f.session_id = ? AND ' => $sessionId,
11978
                    'f.c_id = ? AND ' => intval($this->course_int_id),
11979
                    'f.lp_id = ?' => intval($this->lp_id)
11980
                ]
11981
            ],
11982
            'first'
11983
        );
11984
11985
        if (empty($resultData)) {
11986
            return false;
11987
        }
11988
11989
        return $resultData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $resultData returns the type array which is incompatible with the documented return type boolean.
Loading history...
11990
    }
11991
11992
    /**
11993
     * Create a forum for this learning path
11994
     * @param int $forumCategoryId
11995
     * @return int The forum ID if was created. Otherwise return false
11996
     */
11997
    public function createForum($forumCategoryId)
11998
    {
11999
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12000
12001
        $forumId = store_forum(
12002
            [
12003
                'lp_id' => $this->lp_id,
12004
                'forum_title' => $this->name,
12005
                'forum_comment' => null,
12006
                'forum_category' => intval($forumCategoryId),
12007
                'students_can_edit_group' => ['students_can_edit' => 0],
12008
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12009
                'default_view_type_group' => ['default_view_type' => 'flat'],
12010
                'group_forum' => 0,
12011
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public']
12012
            ],
12013
            [],
12014
            true
12015
        );
12016
12017
        return $forumId;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $forumId returns the type string which is incompatible with the documented return type integer.
Loading history...
12018
    }
12019
12020
    /**
12021
     * Check and obtain the lp final item if exist
12022
     *
12023
     * @return learnpathItem
12024
     */
12025
    private function getFinalItem()
12026
    {
12027
        if (empty($this->items)) {
12028
            return null;
12029
        }
12030
12031
        foreach ($this->items as $item) {
12032
            if ($item->type !== 'final_item') {
12033
                continue;
12034
            }
12035
12036
            return $item;
12037
        }
12038
    }
12039
12040
    /**
12041
     * Get the LP Final Item Template
12042
     *
12043
     * @return string
12044
     */
12045
    private function getFinalItemTemplate()
12046
    {
12047
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
12048
    }
12049
12050
    /**
12051
     * Get the LP Final Item Url
12052
     *
12053
     * @return string
12054
     */
12055
    private function getSavedFinalItem()
12056
    {
12057
        $finalItem = $this->getFinalItem();
12058
        $doc = DocumentManager::get_document_data_by_id(
12059
            $finalItem->path,
12060
            $this->cc
12061
        );
12062
        if ($doc && file_exists($doc['absolute_path'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $doc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
12063
            return file_get_contents($doc['absolute_path']);
12064
        }
12065
12066
        return '';
12067
    }
12068
12069
    /**
12070
     * Get the LP Final Item form
12071
     *
12072
     * @return string
12073
     */
12074
    public function getFinalItemForm()
12075
    {
12076
        $finalItem = $this->getFinalItem();
12077
        $title = '';
12078
12079
        if ($finalItem) {
12080
            $title = $finalItem->get_title();
12081
            $buttonText = get_lang('Save');
12082
            $content = $this->getSavedFinalItem();
12083
        } else {
12084
            $buttonText = get_lang('LPCreateDocument');
12085
            $content = $this->getFinalItemTemplate();
12086
        }
12087
12088
        $courseInfo = api_get_course_info();
12089
        $result = $this->generate_lp_folder($courseInfo);
12090
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12091
        $relative_prefix = '../../';
12092
12093
        $editorConfig = [
12094
            'ToolbarSet' => 'LearningPathDocuments',
12095
            'Width' => '100%',
12096
            'Height' => '500',
12097
            'FullPage' => true,
12098
            'CreateDocumentDir' => $relative_prefix,
12099
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12100
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path
12101
        ];
12102
12103
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12104
            'type' => 'document',
12105
            'lp_id' => $this->lp_id
12106
        ]);
12107
12108
        $form = new FormValidator('final_item', 'POST', $url);
12109
        $form->addText('title', get_lang('Title'));
12110
        $form->addButtonSave($buttonText);
12111
        $form->addHtml(
12112
            Display::return_message(
12113
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12114
                'normal',
12115
                false
12116
            )
12117
        );
12118
12119
        $renderer = $form->defaultRenderer();
12120
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12121
12122
        $form->addHtmlEditor(
12123
            'content_lp_certificate',
12124
            null,
12125
            true,
12126
            false,
12127
            $editorConfig,
12128
            true
12129
        );
12130
        $form->addHidden('action', 'add_final_item');
12131
        $form->addHidden('path', Session::read('pathItem'));
12132
        $form->addHidden('previous', $this->get_last());
12133
        $form->setDefaults(
12134
            ['title' => $title, 'content_lp_certificate' => $content]
12135
        );
12136
12137
        if ($form->validate()) {
12138
            $values = $form->exportValues();
12139
            $lastItemId = $this->get_last();
12140
12141
            if (!$finalItem) {
12142
                $documentId = $this->create_document(
12143
                    $this->course_info,
12144
                    $values['content_lp_certificate'],
12145
                    $values['title']
12146
                );
12147
                $this->add_item(
12148
                    0,
12149
                    $lastItemId,
12150
                    'final_item',
12151
                    $documentId,
12152
                    $values['title'],
12153
                    ''
12154
                );
12155
12156
                Display::addFlash(
12157
                    Display::return_message(get_lang('Added'))
12158
                );
12159
            } else {
12160
                $this->edit_document($this->course_info);
12161
            }
12162
        }
12163
12164
        return $form->returnForm();
12165
    }
12166
12167
    /**
12168
     * Check if the current lp item is first, both, last or none from lp list
12169
     *
12170
     * @param int $currentItemId
12171
     * @return string
12172
     */
12173
    public function isFirstOrLastItem($currentItemId)
12174
    {
12175
        if ($this->debug > 0) {
12176
            error_log('New LP - In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12177
        }
12178
12179
        $lpItemId = [];
12180
        $typeListNotToVerify = self::getChapterTypes();
12181
12182
	    // Using get_toc() function instead $this->items because returns the correct order of the items
12183
        foreach ($this->get_toc() as $item) {
12184
            if (!in_array($item['type'], $typeListNotToVerify)) {
12185
                $lpItemId[] = $item['id'];
12186
            }
12187
        }
12188
12189
        $lastLpItemIndex = count($lpItemId) - 1;
12190
        $position = array_search($currentItemId, $lpItemId);
12191
12192
        switch ($position) {
12193
            case 0:
12194
                if (!$lastLpItemIndex) {
12195
                    $answer = 'both';
12196
                    break;
12197
                }
12198
12199
                $answer = 'first';
12200
                break;
12201
            case $lastLpItemIndex:
12202
                $answer = 'last';
12203
                break;
12204
            default:
12205
                $answer = 'none';
12206
        }
12207
12208
        return $answer;
12209
    }
12210
12211
    /**
12212
     * Get whether this is a learning path with the accumulated SCORM time or not
12213
     * @return int
12214
     */
12215
    public function getAccumulateScormTime()
12216
    {
12217
        return $this->accumulateScormTime;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->accumulateScormTime also could return the type string which is incompatible with the documented return type integer.
Loading history...
12218
    }
12219
12220
    /**
12221
     * Set whether this is a learning path with the accumulated SCORM time or not
12222
     * @param int $value (0 = false, 1 = true)
12223
     * @return bool Always returns true
12224
     */
12225
    public function setAccumulateScormTime($value)
12226
    {
12227
        if ($this->debug > 0) {
12228
            error_log('New LP - In learnpath::setAccumulateScormTime()', 0);
12229
        }
12230
        $this->accumulateScormTime = intval($value);
12231
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12232
        $lp_id = $this->get_id();
12233
        $sql = "UPDATE $lp_table 
12234
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12235
                WHERE iid = $lp_id";
12236
        Database::query($sql);
12237
12238
        return true;
12239
    }
12240
12241
    /**
12242
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12243
     * the new learning path tool.
12244
     *
12245
     * The function is a big switch on tool type.
12246
     * In each case, we query the corresponding table for information and build the link
12247
     * with that information.
12248
     * @author Yannick Warnier <[email protected]> - rebranding based on
12249
     * previous work (display_addedresource_link_in_learnpath())
12250
     * @param int	$course_id Course code
12251
     * @param int $learningPathId The learning path ID (in lp table)
12252
     * @param int $id_in_path the unique index in the items table
12253
     * @param int $lpViewId
12254
     * @param string $origin
12255
     * @return string
12256
     */
12257
    public static function rl_get_resource_link_for_learnpath(
12258
        $course_id,
12259
        $learningPathId,
12260
        $id_in_path,
12261
        $lpViewId,
12262
        $origin = 'learnpath'
12263
    ) {
12264
        $session_id = api_get_session_id();
12265
        $course_info = api_get_course_info_by_id($course_id);
12266
12267
        $learningPathId = intval($learningPathId);
12268
        $id_in_path = intval($id_in_path);
12269
        $lpViewId = intval($lpViewId);
12270
12271
        $em = Database::getManager();
12272
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12273
        /** @var CLpItem $rowItem */
12274
        $rowItem = $lpItemRepo->findOneBy([
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $rowItem is correct as $lpItemRepo->findOneBy(a..., 'id' => $id_in_path)) targeting Doctrine\ORM\EntityRepository::findOneBy() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
12275
            'cId' => $course_id,
12276
            'lpId' => $learningPathId,
12277
            'id' => $id_in_path
12278
        ]);
12279
12280
        if (!$rowItem) {
12281
            return -1;
12282
        }
12283
12284
        $course_code = $course_info['code'];
12285
        $type = $rowItem->getItemType();
12286
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12287
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12288
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12289
        $link = '';
12290
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12291
        switch ($type) {
12292
            case 'dir':
12293
                return $main_dir_path.'lp/blank.php';
12294
            case TOOL_CALENDAR_EVENT:
12295
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12296
            case TOOL_ANNOUNCEMENT:
12297
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12298
            case TOOL_LINK:
12299
                $linkInfo = Link::getLinkInfo($id);
12300
                if (isset($linkInfo['url'])) {
12301
                    return $linkInfo['url'];
12302
                }
12303
                return '';
12304
            case TOOL_QUIZ:
12305
                if (empty($id)) {
12306
                    return '';
12307
                }
12308
12309
                // Get the lp_item_view with the highest view_count.
12310
                $learnpathItemViewResult = $em
12311
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12312
                    ->findBy(
12313
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12314
                        ['viewCount' => 'DESC'],
12315
                        1
12316
                    );
12317
                /** @var CLpItemView $learnpathItemViewData */
12318
                $learnpathItemViewData = current($learnpathItemViewResult);
12319
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12320
12321
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12322
                    .http_build_query([
12323
                        'lp_init' => 1,
12324
                        'learnpath_item_view_id' => $learnpathItemViewId,
12325
                        'learnpath_id' => $learningPathId,
12326
                        'learnpath_item_id' => $id_in_path,
12327
                        'exerciseId' => $id
12328
                    ]);
12329
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12330
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12331
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12332
                $myrow = Database::fetch_array($result);
12333
                $path = $myrow['path'];
12334
12335
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12336
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12337
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12338
            case TOOL_FORUM:
12339
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12340
            case TOOL_THREAD:  //forum post
12341
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12342
                if (empty($id)) {
12343
                    return '';
12344
                }
12345
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12346
                $result = Database::query($sql);
12347
                $myrow = Database::fetch_array($result);
12348
12349
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12350
                    .$extraParams;
12351
            case TOOL_POST:
12352
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12353
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12354
                $myrow = Database::fetch_array($result);
12355
12356
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12357
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12358
            case TOOL_DOCUMENT:
12359
                $document = $em
12360
                    ->getRepository('ChamiloCourseBundle:CDocument')
12361
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12362
12363
                if (!$document) {
12364
                    return '';
12365
                }
12366
12367
                $documentPathInfo = pathinfo($document->getPath());
12368
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12369
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12370
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12371
12372
                $openmethod = 2;
12373
                $officedoc = false;
12374
                Session::write('openmethod', $openmethod);
12375
                Session::write('officedoc', $officedoc);
12376
12377
                if ($showDirectUrl) {
12378
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12379
                }
12380
12381
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12382
            case TOOL_LP_FINAL_ITEM:
12383
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12384
                    .$extraParams;
12385
            case 'assignments':
12386
                return $main_dir_path.'work/work.php?'.$extraParams;
12387
            case TOOL_DROPBOX:
12388
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12389
            case 'introduction_text': //DEPRECATED
12390
                return '';
12391
            case TOOL_COURSE_DESCRIPTION:
12392
                return $main_dir_path.'course_description?'.$extraParams;
12393
            case TOOL_GROUP:
12394
                return $main_dir_path.'group/group.php?'.$extraParams;
12395
            case TOOL_USER:
12396
                return $main_dir_path.'user/user.php?'.$extraParams;
12397
            case TOOL_STUDENTPUBLICATION:
12398
                if (!empty($rowItem->getPath())) {
12399
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12400
                }
12401
12402
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12403
        } //end switch
12404
12405
        return $link;
12406
    }
12407
12408
    /**
12409
     * Gets the name of a resource (generally used in learnpath when no name is provided)
12410
     *
12411
     * @author Yannick Warnier <[email protected]>
12412
     * @param string    Course code
12413
     * @param string    The tool type (using constants declared in main_api.lib.php)
12414
     * @param integer    The resource ID
12415
     * @return string
12416
     */
12417
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12418
    {
12419
        $_course = api_get_course_info($course_code);
12420
        $course_id = $_course['real_id'];
12421
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12422
        $learningPathId = intval($learningPathId);
12423
        $id_in_path = intval($id_in_path);
12424
12425
        $sql_item = "SELECT item_type, title, ref FROM $tbl_lp_item
12426
                     WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12427
        $res_item = Database::query($sql_item);
12428
12429
        if (Database::num_rows($res_item) < 1) {
12430
            return '';
12431
        }
12432
        $row_item = Database::fetch_array($res_item);
12433
        $type = strtolower($row_item['item_type']);
12434
        $id = $row_item['ref'];
12435
        $output = '';
12436
12437
        switch ($type) {
12438
            case TOOL_CALENDAR_EVENT:
12439
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12440
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12441
                $myrow = Database::fetch_array($result);
12442
                $output = $myrow['title'];
12443
                break;
12444
            case TOOL_ANNOUNCEMENT:
12445
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12446
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12447
                $myrow = Database::fetch_array($result);
12448
                $output = $myrow['title'];
12449
                break;
12450
            case TOOL_LINK:
12451
                // Doesn't take $target into account.
12452
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12453
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12454
                $myrow = Database::fetch_array($result);
12455
                $output = $myrow['title'];
12456
                break;
12457
            case TOOL_QUIZ:
12458
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12459
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id=$id");
12460
                $myrow = Database::fetch_array($result);
12461
                $output = $myrow['title'];
12462
                break;
12463
            case TOOL_FORUM:
12464
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12465
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id=$id");
12466
                $myrow = Database::fetch_array($result);
12467
                $output = $myrow['forum_name'];
12468
                break;
12469
            case TOOL_THREAD:  //=topics
12470
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12471
                // Grabbing the title of the post.
12472
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12473
                $result_title = Database::query($sql_title);
12474
                $myrow_title = Database::fetch_array($result_title);
12475
                $output = $myrow_title['post_title'];
12476
                break;
12477
            case TOOL_POST:
12478
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12479
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
12480
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12481
                $result = Database::query($sql);
12482
                $post = Database::fetch_array($result);
12483
                $output = $post['post_title'];
12484
                break;
12485
            case 'dir':
12486
                $title = $row_item['title'];
12487
                if (!empty($title)) {
12488
                    $output = $title;
12489
                } else {
12490
                    $output = '-';
12491
                }
12492
                break;
12493
            case TOOL_DOCUMENT:
12494
                $title = $row_item['title'];
12495
                if (!empty($title)) {
12496
                    $output = $title;
12497
                } else {
12498
                    $output = '-';
12499
                }
12500
                break;
12501
            case 'hotpotatoes':
12502
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12503
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12504
                $myrow = Database::fetch_array($result);
12505
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12506
                $last = count($pathname) - 1; // Making a correct name for the link.
12507
                $filename = $pathname[$last]; // Making a correct name for the link.
12508
                $ext = explode('.', $filename);
12509
                $ext = strtolower($ext[sizeof($ext) - 1]);
0 ignored issues
show
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

12509
                $ext = strtolower($ext[/** @scrutinizer ignore-call */ sizeof($ext) - 1]);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
12510
                $myrow['path'] = rawurlencode($myrow['path']);
12511
                $output = $filename;
12512
                break;
12513
        }
12514
        return stripslashes($output);
12515
    }
12516
12517
    /**
12518
     * Get the parent names for the current item
12519
     * @param int $newItemId Optional. The item ID
12520
     * @return array
12521
     */
12522
    public function getCurrentItemParentNames($newItemId = 0)
12523
    {
12524
        $newItemId = $newItemId ?: $this->get_current_item_id();
12525
        $return = [];
12526
        $item = $this->getItem($newItemId);
12527
        $parent = $this->getItem($item->get_parent());
12528
12529
        while ($parent) {
12530
            $return[] = $parent->get_title();
12531
12532
            $parent = $this->getItem($parent->get_parent());
12533
        }
12534
12535
        return array_reverse($return);
12536
    }
12537
12538
    /**
12539
     * Reads and process "lp_subscription_settings" setting
12540
     * @return array
12541
     */
12542
    public static function getSubscriptionSettings()
12543
    {
12544
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
12545
        if (empty($subscriptionSettings)) {
12546
            // By default allow both settings
12547
            $subscriptionSettings = [
12548
                'allow_add_users_to_lp' => true,
12549
                'allow_add_users_to_lp_category' => true,
12550
            ];
12551
        } else {
12552
            $subscriptionSettings = $subscriptionSettings['options'];
12553
        }
12554
12555
        return $subscriptionSettings;
12556
    }
12557
}
12558
12559