Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

learnpath   F

Complexity

Total Complexity 1766

Size/Duplication

Total Lines 12544
Duplicated Lines 0 %

Importance

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

205 Methods

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

1766
        return count(/** @scrutinizer ignore-type */ $this->items);
Loading history...
1767
    }
1768
1769
    /**
1770
     * Gets the total number of items available for viewing in this SCORM but without chapters
1771
     * @return    integer    The total no-chapters number of items
1772
     */
1773
    public function getTotalItemsCountWithoutDirs()
1774
    {
1775
        if ($this->debug > 0) {
1776
            error_log('New LP - In learnpath::getTotalItemsCountWithoutDirs()', 0);
1777
        }
1778
        $total = 0;
1779
        $typeListNotToCount = self::getChapterTypes();
1780
        foreach ($this->items as $temp2) {
1781
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1782
                $total++;
1783
            }
1784
        }
1785
        return $total;
1786
    }
1787
1788
    /**
1789
    *  Sets the first element URL.
1790
     */
1791
    public function first()
1792
    {
1793
        if ($this->debug > 0) {
1794
            error_log('New LP - In learnpath::first()', 0);
1795
            error_log('$this->last_item_seen '.$this->last_item_seen);
1796
        }
1797
1798
        // Test if the last_item_seen exists and is not a dir.
1799
        if (count($this->ordered_items) == 0) {
1800
            $this->index = 0;
1801
        }
1802
1803
        if ($this->debug > 0) {
1804
            if (isset($this->items[$this->last_item_seen])) {
1805
                $status = $this->items[$this->last_item_seen]->get_status();
1806
            }
1807
        }
1808
1809
        if (!empty($this->last_item_seen) &&
1810
            !empty($this->items[$this->last_item_seen]) &&
1811
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1812
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1813
            //&& !$this->items[$this->last_item_seen]->is_done()
1814
        ) {
1815
            if ($this->debug > 2) {
1816
                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);
1817
            }
1818
            $index = -1;
1819
            foreach ($this->ordered_items as $myindex => $item_id) {
1820
                if ($item_id == $this->last_item_seen) {
1821
                    $index = $myindex;
1822
                    break;
1823
                }
1824
            }
1825
            if ($index == -1) {
1826
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1827
                if ($this->debug > 2) {
1828
                    error_log('New LP - Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1829
                }
1830
                return false;
1831
            } else {
1832
                $this->last     = $this->last_item_seen;
1833
                $this->current  = $this->last_item_seen;
1834
                $this->index    = $index;
1835
            }
1836
        } else {
1837
            if ($this->debug > 2) {
1838
                error_log('New LP - In learnpath::first() - No last item seen', 0);
1839
            }
1840
            $index = 0;
1841
            // Loop through all ordered items and stop at the first item that is
1842
            // not a directory *and* that has not been completed yet.
1843
            while (!empty($this->ordered_items[$index]) &&
1844
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1845
                (
1846
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1847
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1848
                ) && $index < $this->max_ordered_items) {
1849
                $index++;
1850
            }
1851
            $this->last = $this->current;
1852
            // current is
1853
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1854
            $this->index = $index;
1855
            if ($this->debug > 2) {
1856
                error_log('$index '.$index);
1857
                error_log('New LP - In learnpath::first() - No last item seen. New last = '.$this->last.'('.$this->ordered_items[$index].')', 0);
1858
            }
1859
        }
1860
        if ($this->debug > 2) {
1861
            error_log('New LP - In learnpath::first() - First item is '.$this->get_current_item_id());
1862
        }
1863
    }
1864
1865
    /**
1866
     * Gets the information about an item in a format usable as JavaScript to update
1867
     * the JS API by just printing this content into the <head> section of the message frame
1868
     * @param   int $item_id
1869
     * @return  string
1870
     */
1871
    public function get_js_info($item_id = 0)
1872
    {
1873
        if ($this->debug > 0) {
1874
            error_log('New LP - In learnpath::get_js_info('.$item_id.')', 0);
1875
        }
1876
1877
        $info = '';
1878
        $item_id = intval($item_id);
1879
1880
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1881
            //if item is defined, return values from DB
1882
            $oItem = $this->items[$item_id];
1883
            $info .= '<script language="javascript">';
1884
            $info .= "top.set_score(".$oItem->get_score().");\n";
1885
            $info .= "top.set_max(".$oItem->get_max().");\n";
1886
            $info .= "top.set_min(".$oItem->get_min().");\n";
1887
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1888
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1889
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1890
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1891
            $info .= "top.set_flag_synchronized();";
1892
            $info .= '</script>';
1893
            if ($this->debug > 2) {
1894
                error_log('New LP - in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1895
            }
1896
            return $info;
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), [
2094
                'dll',
2095
                'exe'
2096
            ])) {
2097
            return 'oogie';
2098
        }
2099
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
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
                        ['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
                [
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 [
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 = [
2765
                    '&',
2766
                    '|',
2767
                    '~',
2768
                    '=',
2769
                    '<>',
2770
                    '{',
2771
                    '}',
2772
                    '*',
2773
                    '(',
2774
                    ')'
2775
                ];
2776
                $replace = [
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
                    return $val;
2826
                }
2827
            }
2828
        }
2829
2830
        return false;
2831
    }
2832
2833
    /**
2834
     * Gets the status list for all LP's items
2835
     * @return	array	Array of [index] => [item ID => current status]
2836
     */
2837
    public function get_items_status_list()
2838
    {
2839
        if ($this->debug > 0) {
2840
            error_log('New LP - In learnpath::get_items_status_list()', 0);
2841
        }
2842
        $list = [];
2843
        foreach ($this->ordered_items as $item_id) {
2844
            $list[] = [
2845
                $item_id => $this->items[$item_id]->get_status()
2846
            ];
2847
        }
2848
        return $list;
2849
    }
2850
2851
    /**
2852
     * Return the number of interactions for the given learnpath Item View ID.
2853
     * This method can be used as static.
2854
     * @param	integer	Item View ID
2855
     * @param   integer course id
2856
     * @return	integer	Number of interactions
2857
     */
2858
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2859
    {
2860
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2861
        $lp_iv_id = intval($lp_iv_id);
2862
        $course_id = intval($course_id);
2863
2864
        $sql = "SELECT count(*) FROM $table
2865
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2866
        $res = Database::query($sql);
2867
        $num = 0;
2868
        if (Database::num_rows($res)) {
2869
            $row = Database::fetch_array($res);
2870
            $num = $row[0];
2871
        }
2872
        return $num;
2873
    }
2874
2875
    /**
2876
     * Return the interactions as an array for the given lp_iv_id.
2877
     * This method can be used as static.
2878
     * @param	integer	Learnpath Item View ID
2879
     * @return	array
2880
     * @todo 	Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2881
     */
2882
    public static function get_iv_interactions_array($lp_iv_id)
2883
    {
2884
        $course_id = api_get_course_int_id();
2885
        $list = [];
2886
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2887
2888
        if (empty($lp_iv_id)) {
2889
            return [];
2890
        }
2891
2892
        $sql = "SELECT * FROM $table
2893
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2894
                ORDER BY order_id ASC";
2895
        $res = Database::query($sql);
2896
        $num = Database::num_rows($res);
2897
        if ($num > 0) {
2898
            $list[] = [
2899
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2900
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2901
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2902
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2903
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2904
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2905
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2906
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES)
2907
            ];
2908
            while ($row = Database::fetch_array($res)) {
2909
                $list[] = [
2910
                    'order_id' => ($row['order_id'] + 1),
2911
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2912
                    'type' => $row['interaction_type'],
2913
                    'time' => $row['completion_time'],
2914
                    //'correct_responses' => $row['correct_responses'],
2915
                    'correct_responses' => '', // Hide correct responses from students.
2916
                    'student_response' => $row['student_response'],
2917
                    'result' => $row['result'],
2918
                    'latency' => $row['latency']
2919
                ];
2920
            }
2921
        }
2922
2923
        return $list;
2924
    }
2925
2926
    /**
2927
     * Return the number of objectives for the given learnpath Item View ID.
2928
     * This method can be used as static.
2929
     * @param	integer	Item View ID
2930
     * @return	integer	Number of objectives
2931
     */
2932
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2933
    {
2934
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2935
        $course_id = intval($course_id);
2936
        $lp_iv_id = intval($lp_iv_id);
2937
        $sql = "SELECT count(*) FROM $table
2938
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2939
        //@todo seems that this always returns 0
2940
        $res = Database::query($sql);
2941
        $num = 0;
2942
        if (Database::num_rows($res)) {
2943
            $row = Database::fetch_array($res);
2944
            $num = $row[0];
2945
        }
2946
2947
        return $num;
2948
    }
2949
2950
    /**
2951
     * Return the objectives as an array for the given lp_iv_id.
2952
     * This method can be used as static.
2953
     * @param	integer	Learnpath Item View ID
2954
     * @return	array
2955
     * @todo 	Translate labels
2956
     */
2957
    public static function get_iv_objectives_array($lp_iv_id = 0)
2958
    {
2959
        $course_id = api_get_course_int_id();
2960
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2961
        $sql = "SELECT * FROM $table
2962
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id
2963
                ORDER BY order_id ASC";
2964
        $res = Database::query($sql);
2965
        $num = Database::num_rows($res);
2966
        $list = [];
2967
        if ($num > 0) {
2968
            $list[] = [
2969
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2970
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
2971
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
2972
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
2973
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
2974
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES)
2975
            ];
2976
            while ($row = Database::fetch_array($res)) {
2977
                $list[] = [
2978
                    'order_id' => ($row['order_id'] + 1),
2979
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2980
                    'score_raw' => $row['score_raw'],
2981
                    'score_max' => $row['score_max'],
2982
                    'score_min' => $row['score_min'],
2983
                    'status' => $row['status']
2984
                ];
2985
            }
2986
        }
2987
2988
        return $list;
2989
    }
2990
2991
    /**
2992
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2993
     * used by get_html_toc() to be ready to display
2994
     * @return	array	TOC as a table with 4 elements per row: title, link, status and level
2995
     */
2996
    public function get_toc()
2997
    {
2998
        if ($this->debug > 0) {
2999
            error_log('learnpath::get_toc()', 0);
3000
        }
3001
        $toc = [];
3002
        foreach ($this->ordered_items as $item_id) {
3003
            if ($this->debug > 2) {
3004
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3005
            }
3006
            // TODO: Change this link generation and use new function instead.
3007
            $toc[] = [
3008
                'id' => $item_id,
3009
                'title' => $this->items[$item_id]->get_title(),
3010
                'status' => $this->items[$item_id]->get_status(),
3011
                'level' => $this->items[$item_id]->get_level(),
3012
                'type' => $this->items[$item_id]->get_type(),
3013
                'description' => $this->items[$item_id]->get_description(),
3014
                'path' => $this->items[$item_id]->get_path(),
3015
                'parent' => $this->items[$item_id]->get_parent(),
3016
            ];
3017
        }
3018
        if ($this->debug > 2) {
3019
            error_log('New LP - In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3020
        }
3021
        return $toc;
3022
    }
3023
3024
    /**
3025
     * Generate and return the table of contents for this learnpath. The JS
3026
     * table returned is used inside of scorm_api.php
3027
     * @param string $varname
3028
     * @return  string  A JS array vairiable construction
3029
     */
3030
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3031
    {
3032
        if ($this->debug > 0) {
3033
            error_log('New LP - In learnpath::get_items_details_as_js()', 0);
3034
        }
3035
        $toc = $varname.' = new Array();';
3036
        foreach ($this->ordered_items as $item_id) {
3037
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3038
        }
3039
        if ($this->debug > 2) {
3040
            error_log('New LP - In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3041
        }
3042
        return $toc;
3043
    }
3044
3045
    /**
3046
     * Gets the learning path type
3047
     * @param	boolean		Return the name? If false, return the ID. Default is false.
3048
     * @return	mixed		Type ID or name, depending on the parameter
3049
     */
3050
    public function get_type($get_name = false)
3051
    {
3052
        $res = false;
3053
        if ($this->debug > 0) {
3054
            error_log('New LP - In learnpath::get_type()', 0);
3055
        }
3056
        if (!empty($this->type) && (!$get_name)) {
3057
            $res = $this->type;
3058
        }
3059
        if ($this->debug > 2) {
3060
            error_log('New LP - In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3061
        }
3062
        return $res;
3063
    }
3064
3065
    /**
3066
     * Gets the learning path type as static method
3067
     * @param	int $lp_id
3068
     * @return	mixed		Type ID or name, depending on the parameter
3069
     */
3070
    public static function get_type_static($lp_id = 0)
3071
    {
3072
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3073
        $lp_id = intval($lp_id);
3074
        $sql = "SELECT lp_type FROM $tbl_lp
3075
                WHERE iid = $lp_id";
3076
        $res = Database::query($sql);
3077
        if ($res === false) {
3078
            return null;
3079
        }
3080
        if (Database::num_rows($res) <= 0) {
3081
            return null;
3082
        }
3083
        $row = Database::fetch_array($res);
3084
        return $row['lp_type'];
3085
    }
3086
3087
    /**
3088
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3089
     * This method can be used as abstract and is recursive
3090
     * @param	integer	Learnpath ID
3091
     * @param	integer	Parent ID of the items to look for
3092
     * @return	array	Ordered list of item IDs (empty array on error)
3093
     */
3094
    public static function get_flat_ordered_items_list($lp, $parent = 0, $course_id = 0)
3095
    {
3096
        if (empty($course_id)) {
3097
            $course_id = api_get_course_int_id();
3098
        } else {
3099
            $course_id = intval($course_id);
3100
        }
3101
        $list = [];
3102
3103
        if (empty($lp)) {
3104
            return $list;
3105
        }
3106
3107
        $lp = intval($lp);
3108
        $parent = intval($parent);
3109
3110
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3111
        $sql = "SELECT iid FROM $tbl_lp_item
3112
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3113
                ORDER BY display_order";
3114
3115
        $res = Database::query($sql);
3116
        while ($row = Database::fetch_array($res)) {
3117
            $sublist = self::get_flat_ordered_items_list(
3118
                $lp,
3119
                $row['iid'],
3120
                $course_id
3121
            );
3122
            $list[] = $row['iid'];
3123
            foreach ($sublist as $item) {
3124
                $list[] = $item;
3125
            }
3126
        }
3127
        return $list;
3128
    }
3129
3130
    /**
3131
     * @return array
3132
     */
3133
    public static function getChapterTypes()
3134
    {
3135
        return [
3136
            'dir'
3137
        ];
3138
    }
3139
3140
    /**
3141
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display
3142
     * @return	string	HTML TOC ready to display
3143
     */
3144
    public function getParentToc($tree)
3145
    {
3146
        if ($this->debug > 0) {
3147
            error_log('In learnpath::get_html_toc()', 0);
3148
        }
3149
        if (empty($tree)) {
3150
            $tree = $this->get_toc();
3151
        }
3152
        $dirTypes = self::getChapterTypes();
3153
        $myCurrentId = $this->get_current_item_id();
3154
        $listParent = [];
3155
        $listChildren = [];
3156
        $listNotParent = [];
3157
        $list = [];
3158
        foreach ($tree as $subtree) {
3159
            if (in_array($subtree['type'], $dirTypes)) {
3160
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3161
                $subtree['children'] = $listChildren;
3162
                if (!empty($subtree['children'])) {
3163
                    foreach ($subtree['children'] as $subItem) {
3164
                        if ($subItem['id'] == $this->current) {
3165
                            $subtree['parent_current'] = 'in';
3166
                            $subtree['current'] = 'on';
3167
                        }
3168
                    }
3169
                }
3170
                $listParent[] =  $subtree;
3171
            }
3172
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3173
                $classStatus = [
3174
                    'not attempted' => 'scorm_not_attempted',
3175
                    'incomplete' => 'scorm_not_attempted',
3176
                    'failed' => 'scorm_failed',
3177
                    'completed' => 'scorm_completed',
3178
                    'passed' => 'scorm_completed',
3179
                    'succeeded' => 'scorm_completed',
3180
                    'browsed' => 'scorm_completed',
3181
                ];
3182
3183
                if (isset($classStatus[$subtree['status']])) {
3184
                    $cssStatus = $classStatus[$subtree['status']];
3185
                }
3186
3187
                $title = Security::remove_XSS($subtree['title']);
3188
                unset($subtree['title']);
3189
3190
                if (empty($title)) {
3191
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3192
                }
3193
                $classStyle = null;
3194
                if ($subtree['id'] == $this->current) {
3195
                    $classStyle = 'scorm_item_normal '. $classStyle . 'scorm_highlight';
3196
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3197
                    $classStyle = 'scorm_item_normal '. $classStyle . ' ';
3198
                }
3199
                $subtree['title'] = $title;
3200
                $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...
3201
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3202
                $subtree['current_id'] = $myCurrentId;
3203
                $listNotParent[] = $subtree;
3204
            }
3205
        }
3206
3207
        $list['are_parents'] = $listParent;
3208
        $list['not_parents'] = $listNotParent;
3209
3210
        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...
3211
    }
3212
3213
    /**
3214
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display
3215
     * @return	string	HTML TOC ready to display
3216
     */
3217
    public function getChildrenToc($tree, $id, $parent = true)
3218
    {
3219
        if ($this->debug > 0) {
3220
            error_log('In learnpath::get_html_toc()', 0);
3221
        }
3222
        if (empty($tree)) {
3223
            $tree = $this->get_toc();
3224
        }
3225
3226
        $dirTypes = self::getChapterTypes();
3227
        $mycurrentitemid = $this->get_current_item_id();
3228
        $list = [];
3229
        $classStatus = [
3230
            'not attempted' => 'scorm_not_attempted',
3231
            'incomplete' => 'scorm_not_attempted',
3232
            'failed' => 'scorm_failed',
3233
            'completed' => 'scorm_completed',
3234
            'passed' => 'scorm_completed',
3235
            'succeeded' => 'scorm_completed',
3236
            'browsed' => 'scorm_completed',
3237
        ];
3238
3239
        foreach ($tree as $subtree) {
3240
            $subtree['tree'] = null;
3241
3242
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3243
                if ($subtree['id'] == $this->current) {
3244
                    $subtree['current'] = 'active';
3245
                } else {
3246
                    $subtree['current'] = null;
3247
                }
3248
                if (isset($classStatus[$subtree['status']])) {
3249
                    $cssStatus = $classStatus[$subtree['status']];
3250
                }
3251
3252
                $title = Security::remove_XSS($subtree['title']);
3253
                unset($subtree['title']);
3254
                if (empty($title)) {
3255
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3256
                }
3257
3258
                $classStyle = null;
3259
                if ($subtree['id'] == $this->current) {
3260
                    $classStyle = 'scorm_item_normal '. $classStyle . 'scorm_highlight';
3261
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3262
                    $classStyle = 'scorm_item_normal '. $classStyle . ' ';
3263
                }
3264
3265
                if (in_array($subtree['type'], $dirTypes)) {
3266
                    $subtree['title'] = stripslashes($title);
3267
                } else {
3268
                    $subtree['title'] = $title;
3269
                    $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...
3270
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3271
                    $subtree['current_id'] = $mycurrentitemid;
3272
                }
3273
                $list[] =  $subtree;
3274
            }
3275
        }
3276
3277
        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...
3278
    }
3279
3280
    /**
3281
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display
3282
     * @param array $toc_list
3283
     * @return	string	HTML TOC ready to display
3284
     */
3285
    public function getListArrayToc($toc_list = [])
3286
    {
3287
        if ($this->debug > 0) {
3288
            error_log('In learnpath::get_html_toc()', 0);
3289
        }
3290
        if (empty($toc_list)) {
3291
            $toc_list = $this->get_toc();
3292
        }
3293
        // Temporary variables.
3294
        $mycurrentitemid = $this->get_current_item_id();
3295
        $list = [];
3296
        $arrayList = [];
3297
        $classStatus = [
3298
            'not attempted' => 'scorm_not_attempted',
3299
            'incomplete' => 'scorm_not_attempted',
3300
            'failed' => 'scorm_failed',
3301
            'completed' => 'scorm_completed',
3302
            'passed' => 'scorm_completed',
3303
            'succeeded' => 'scorm_completed',
3304
            'browsed' => 'scorm_completed',
3305
        ];
3306
3307
        foreach ($toc_list as $item) {
3308
            $list['id'] = $item['id'];
3309
            $list['status'] = $item['status'];
3310
            $cssStatus = null;
3311
3312
            if (isset($classStatus[$item['status']])) {
3313
                $cssStatus = $classStatus[$item['status']];
3314
            }
3315
3316
            $classStyle = ' ';
3317
            $dirTypes = self::getChapterTypes();
3318
3319
            if (in_array($item['type'], $dirTypes)) {
3320
                $classStyle = 'scorm_item_section ';
3321
            }
3322
            if ($item['id'] == $this->current) {
3323
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3324
            } elseif (!in_array($item['type'], $dirTypes)) {
3325
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3326
            }
3327
            $title = $item['title'];
3328
            if (empty($title)) {
3329
                $title = self::rl_get_resource_name(
3330
                    api_get_course_id(),
3331
                    $this->get_id(),
3332
                    $item['id']
3333
                );
3334
            }
3335
            $title = Security::remove_XSS($item['title']);
3336
3337
            if (empty($item['description'])) {
3338
                $list['description'] = $title;
3339
            } else {
3340
                $list['description'] = $item['description'];
3341
            }
3342
3343
            $list['class'] = $classStyle.' '.$cssStatus;
3344
            $list['level'] = $item['level'];
3345
            $list['type'] = $item['type'];
3346
3347
            if (in_array($item['type'], $dirTypes)) {
3348
                $list['css_level'] = 'level_'.$item['level'];
3349
            } else {
3350
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.learnpath::format_scorm_type_item($item['type']);
3351
            }
3352
3353
            if (in_array($item['type'], $dirTypes)) {
3354
                $list['title'] = stripslashes($title);
3355
            } else {
3356
                $list['title'] = stripslashes($title);
3357
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3358
                $list['current_id'] = $mycurrentitemid;
3359
            }
3360
            $arrayList[] = $list;
3361
        }
3362
3363
        return $arrayList;
3364
    }
3365
3366
    /**
3367
     * Returns an HTML-formatted string ready to display with teacher buttons
3368
     * in LP view menu
3369
     * @return	string	HTML TOC ready to display
3370
     */
3371
    public function get_teacher_toc_buttons()
3372
    {
3373
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true, false, false);
3374
        $hide_teacher_icons_lp = api_get_configuration_value('hide_teacher_icons_lp');
3375
        $html = '';
3376
        if ($is_allowed_to_edit && $hide_teacher_icons_lp == false) {
3377
            if ($this->get_lp_session_id() == api_get_session_id()) {
3378
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3379
                $html .= '<div class="btn-group">';
3380
                $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'>".
3381
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3382
                $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'>".
3383
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3384
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3385
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3386
                $html .= '</div>';
3387
                $html .= '</div>';
3388
            }
3389
        }
3390
3391
        return $html;
3392
    }
3393
3394
    /**
3395
     * Gets the learnpath maker name - generally the editor's name
3396
     * @return	string	Learnpath maker name
3397
     */
3398
    public function get_maker()
3399
    {
3400
        if ($this->debug > 0) {
3401
            error_log('New LP - In learnpath::get_maker()', 0);
3402
        }
3403
        if (!empty($this->maker)) {
3404
            return $this->maker;
3405
        } else {
3406
            return '';
3407
        }
3408
    }
3409
3410
    /**
3411
     * Gets the learnpath name/title
3412
     * @return	string	Learnpath name/title
3413
     */
3414
    public function get_name()
3415
    {
3416
        if ($this->debug > 0) {
3417
            error_log('New LP - In learnpath::get_name()', 0);
3418
        }
3419
        if (!empty($this->name)) {
3420
            return $this->name;
3421
        } else {
3422
            return 'N/A';
3423
        }
3424
    }
3425
3426
    /**
3427
     * Gets a link to the resource from the present location, depending on item ID.
3428
     * @param	string	$type Type of link expected
3429
     * @param	integer	$item_id Learnpath item ID
3430
     * @return	string	$provided_toc Link to the lp_item resource
3431
     */
3432
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3433
    {
3434
        $course_id = $this->get_course_int_id();
3435
3436
        if ($this->debug > 0) {
3437
            error_log('New LP - In learnpath::get_link('.$type.','.$item_id.')', 0);
3438
        }
3439
        if (empty($item_id)) {
3440
            if ($this->debug > 2) {
3441
                error_log('New LP - In learnpath::get_link() - no item id given in learnpath::get_link(), using current: '.$this->get_current_item_id(), 0);
3442
            }
3443
            $item_id = $this->get_current_item_id();
3444
        }
3445
3446
        if (empty($item_id)) {
3447
            if ($this->debug > 2) {
3448
                error_log('New LP - In learnpath::get_link() - no current item id found in learnpath object', 0);
3449
            }
3450
            //still empty, this means there was no item_id given and we are not in an object context or
3451
            //the object property is empty, return empty link
3452
            $item_id = $this->first();
3453
            return '';
3454
        }
3455
3456
        $file = '';
3457
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3458
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3459
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3460
        $item_id = intval($item_id);
3461
3462
        $sql = "SELECT
3463
                    l.lp_type as ltype,
3464
                    l.path as lpath,
3465
                    li.item_type as litype,
3466
                    li.path as lipath,
3467
                    li.parameters as liparams
3468
        		FROM $lp_table l
3469
                INNER JOIN $lp_item_table li
3470
                ON (li.lp_id = l.iid)
3471
        		WHERE 
3472
        		    li.iid = $item_id 
3473
        		";
3474
        if ($this->debug > 2) {
3475
            error_log('New LP - In learnpath::get_link() - selecting item '.$sql, 0);
3476
        }
3477
        $res = Database::query($sql);
3478
        if (Database::num_rows($res) > 0) {
3479
            $row = Database::fetch_array($res);
3480
            $lp_type = $row['ltype'];
3481
            $lp_path = $row['lpath'];
3482
            $lp_item_type = $row['litype'];
3483
            $lp_item_path = $row['lipath'];
3484
            $lp_item_params = $row['liparams'];
3485
3486
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3487
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3488
            }
3489
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3490
            if ($type == 'http') {
3491
                //web path
3492
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3493
            } else {
3494
                $course_path = $sys_course_path; //system path
3495
            }
3496
3497
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3498
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3499
            if (in_array(
3500
                $lp_item_type,
3501
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3502
            )
3503
            ) {
3504
                $lp_type = 1;
3505
            }
3506
3507
            if ($this->debug > 2) {
3508
                error_log('New LP - In learnpath::get_link() - $lp_type '.$lp_type, 0);
3509
                error_log('New LP - In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3510
            }
3511
3512
            // Now go through the specific cases to get the end of the path
3513
            // @todo Use constants instead of int values.
3514
            switch ($lp_type) {
3515
                case 1:
3516
                    $file = self::rl_get_resource_link_for_learnpath(
3517
                        $course_id,
3518
                        $this->get_id(),
3519
                        $item_id,
3520
                        $this->get_view_id()
3521
                    );
3522
                    if ($this->debug > 0) {
3523
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3524
                    }
3525
3526
                    switch ($lp_item_type) {
3527
                        case 'dir':
3528
                            $file = 'lp_content.php?type=dir';
3529
                            break;
3530
                        case 'link':
3531
                            if (Link::is_youtube_link($file)) {
3532
                                $src = Link::get_youtube_video_id($file);
3533
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3534
                            } elseif (Link::isVimeoLink($file)) {
3535
                                $src = Link::getVimeoLinkId($file);
3536
                                $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

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

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

5890
        if (count(/** @scrutinizer ignore-type */ $this->items) == 0) {
Loading history...
5891
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5892
        } else {
5893
            $return_audio = '<table class="data_table">';
5894
            $return_audio .= '<tr>';
5895
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5896
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5897
            $return_audio .= '</tr>';
5898
5899
            if ($update_audio != 'true') {
5900
                $return .= '<div class="col-md-12">';
5901
                $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

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

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

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

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

6849
                    /** @scrutinizer ignore-type */ $urlAppend.api_get_path(REL_COURSE_PATH),
Loading history...
6850
                    $content
6851
                );
6852
                // Change the path of mp3 to absolute.
6853
                // The first regexp deals with :// urls.
6854
                $content = preg_replace("|(flashvars=\"file=)([^:/]+)/|", "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/', $content);
6855
                // The second regexp deals with audio/ urls.
6856
                $content = preg_replace("|(flashvars=\"file=)([^:/]+)/|", "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/', $content);
6857
                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

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

8178
            $parent_select->/** @scrutinizer ignore-call */ 
8179
                            addOption(
Loading history...
8179
                $value['value'],
8180
                $key,
8181
                'style="padding-left:'.$value['padding'].'px;"'
8182
            );
8183
        }
8184
        if (!empty($s_selected_parent)) {
8185
            $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

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

10125
            while (false !== ($file = readdir(/** @scrutinizer ignore-type */ $handle))) {
Loading history...
10126
                if ($file != '.' && $file != '..') {
10127
                    unlink("$temp_zip_dir/$file");
10128
                }
10129
            }
10130
            closedir($handle);
10131
        }
10132
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10133
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10134
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10135
        ) {
10136
            // Remove the possible . at the end of the path.
10137
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10138
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10139
            mkdir(
10140
                $dest_path_to_scorm_folder,
10141
                api_get_permissions_for_new_directories(),
10142
                true
10143
            );
10144
            copyr(
10145
                $current_course_path.'/scorm/'.$this->path,
10146
                $dest_path_to_scorm_folder,
10147
                ['imsmanifest'],
10148
                $zip_files
10149
            );
10150
        }
10151
10152
        // Build a dummy imsmanifest structure.
10153
        // Do not add to the zip yet (we still need it).
10154
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10155
        // Aggregation Model official document, section "2.3 Content Packaging".
10156
        // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
10157
        $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

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

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

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

11160
                $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...
11161
                $filename = uniqid('');
11162
                $new_file_name = $filename.'.'.$file_extension;
11163
                $new_path = $updir.'/'.$new_file_name;
11164
11165
                // Resize the image.
11166
                $temp = new Image($image_array['tmp_name']);
11167
                $temp->resize(104);
11168
                $result = $temp->send_image($new_path);
11169
11170
                // Storing the image filename.
11171
                if ($result) {
11172
                    $this->set_preview_image($new_file_name);
11173
11174
                    //Resize to 64px to use on course homepage
11175
                    $temp->resize(64);
11176
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11177
                    return true;
11178
                }
11179
            }
11180
        }
11181
11182
        return false;
11183
    }
11184
11185
    /**
11186
     * @param int $lp_id
11187
     * @param string $status
11188
     */
11189
    public function set_autolaunch($lp_id, $status)
11190
    {
11191
        $course_id = api_get_course_int_id();
11192
        $lp_id = intval($lp_id);
11193
        $status = intval($status);
11194
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11195
11196
        // Setting everything to autolaunch = 0
11197
        $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...
11198
        $where = [
11199
            'session_id = ? AND c_id = ? ' => [
11200
                api_get_session_id(),
11201
                $course_id
11202
            ]
11203
        ];
11204
        Database::update($lp_table, $attributes, $where);
11205
        if ($status == 1) {
11206
            //Setting my lp_id to autolaunch = 1
11207
            $attributes['autolaunch'] = 1;
11208
            $where = [
11209
                'iid = ? AND session_id = ? AND c_id = ?' => [
11210
                    $lp_id,
11211
                    api_get_session_id(),
11212
                    $course_id
11213
                ]
11214
            ];
11215
            Database::update($lp_table, $attributes, $where);
11216
        }
11217
    }
11218
11219
    /**
11220
     * Gets previous_item_id for the next element of the lp_item table
11221
     * @author Isaac flores paz
11222
     * @return	integer	Previous item ID
11223
     */
11224
    public function select_previous_item_id()
11225
    {
11226
        $course_id = api_get_course_int_id();
11227
        if ($this->debug > 0) {
11228
            error_log('New LP - In learnpath::select_previous_item_id()', 0);
11229
        }
11230
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11231
11232
        // Get the max order of the items
11233
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11234
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11235
        $rs_max_order = Database::query($sql);
11236
        $row_max_order = Database::fetch_object($rs_max_order);
11237
        $max_order = $row_max_order->display_order;
11238
        // Get the previous item ID
11239
        $sql = "SELECT iid as previous FROM $table_lp_item
11240
                WHERE 
11241
                    c_id = $course_id AND 
11242
                    lp_id = ".$this->lp_id." AND 
11243
                    display_order = '$max_order' ";
11244
        $rs_max = Database::query($sql);
11245
        $row_max = Database::fetch_object($rs_max);
11246
11247
        // Return the previous item ID
11248
        return $row_max->previous;
11249
    }
11250
11251
    /**
11252
     * Copies an LP
11253
     */
11254
    public function copy()
11255
    {
11256
        // Course builder
11257
        $cb = new CourseBuilder();
11258
11259
        //Setting tools that will be copied
11260
        $cb->set_tools_to_build(['learnpaths']);
11261
11262
        //Setting elements that will be copied
11263
        $cb->set_tools_specific_id_list(
11264
            ['learnpaths' => [$this->lp_id]]
11265
        );
11266
11267
        $course = $cb->build();
11268
11269
        //Course restorer
11270
        $course_restorer = new CourseRestorer($course);
11271
        $course_restorer->set_add_text_in_items(true);
11272
        $course_restorer->set_tool_copy_settings(
11273
            ['learnpaths' => ['reset_dates' => true]]
11274
        );
11275
        $course_restorer->restore(
11276
            api_get_course_id(),
11277
            api_get_session_id(),
11278
            false,
11279
            false
11280
        );
11281
    }
11282
11283
    /**
11284
     * Verify document size
11285
     * @param string $s
11286
     * @return bool
11287
     */
11288
    public static function verify_document_size($s)
11289
    {
11290
        $post_max = ini_get('post_max_size');
11291
        if (substr($post_max, -1, 1) == 'M') {
11292
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11293
        } elseif (substr($post_max, -1, 1) == 'G') {
11294
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11295
        }
11296
        $upl_max = ini_get('upload_max_filesize');
11297
        if (substr($upl_max, -1, 1) == 'M') {
11298
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11299
        } elseif (substr($upl_max, -1, 1) == 'G') {
11300
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11301
        }
11302
        $documents_total_space = DocumentManager::documents_total_space();
11303
        $course_max_space = DocumentManager::get_course_quota();
11304
        $total_size = filesize($s) + $documents_total_space;
11305
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11306
            return true;
11307
        } else {
11308
            return false;
11309
        }
11310
    }
11311
11312
    /**
11313
     * Clear LP prerequisites
11314
     */
11315
    public function clear_prerequisites()
11316
    {
11317
        $course_id = $this->get_course_int_id();
11318
        if ($this->debug > 0) {
11319
            error_log('New LP - In learnpath::clear_prerequisites()', 0);
11320
        }
11321
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11322
        $lp_id = $this->get_id();
11323
        //Cleaning prerequisites
11324
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11325
                WHERE c_id = $course_id AND lp_id = $lp_id";
11326
        Database::query($sql);
11327
11328
        //Cleaning mastery score for exercises
11329
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11330
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11331
        Database::query($sql);
11332
    }
11333
11334
    public function set_previous_step_as_prerequisite_for_all_items()
11335
    {
11336
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11337
        $course_id = $this->get_course_int_id();
11338
        $lp_id = $this->get_id();
11339
11340
        if (!empty($this->items)) {
11341
            $previous_item_id = null;
11342
            $previous_item_max = 0;
11343
            $previous_item_type = null;
11344
            $last_item_not_dir = null;
11345
            $last_item_not_dir_type = null;
11346
            $last_item_not_dir_max = null;
11347
11348
            foreach ($this->ordered_items as $itemId) {
11349
                $item = $this->getItem($itemId);
11350
                // if there was a previous item... (otherwise jump to set it)
11351
                if (!empty($previous_item_id)) {
11352
                    $current_item_id = $item->get_id(); //save current id
11353
                    if ($item->get_type() != 'dir') {
11354
                        // Current item is not a folder, so it qualifies to get a prerequisites
11355
                        if ($last_item_not_dir_type == 'quiz') {
11356
                            // if previous is quiz, mark its max score as default score to be achieved
11357
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11358
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11359
                            Database::query($sql);
11360
                        }
11361
                        // now simply update the prerequisite to set it to the last non-chapter item
11362
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11363
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11364
                        Database::query($sql);
11365
                        // record item as 'non-chapter' reference
11366
                        $last_item_not_dir = $item->get_id();
11367
                        $last_item_not_dir_type = $item->get_type();
11368
                        $last_item_not_dir_max = $item->get_max();
11369
                    }
11370
                } else {
11371
                    if ($item->get_type() != 'dir') {
11372
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11373
                        $last_item_not_dir = $item->get_id();
11374
                        $last_item_not_dir_type = $item->get_type();
11375
                        $last_item_not_dir_max = $item->get_max();
11376
                    }
11377
                }
11378
                // Saving the item as "previous item" for the next loop
11379
                $previous_item_id = $item->get_id();
11380
                $previous_item_max = $item->get_max();
11381
                $previous_item_type = $item->get_type();
11382
            }
11383
        }
11384
    }
11385
11386
    /**
11387
     * @param array $params
11388
     */
11389
    public static function createCategory($params)
11390
    {
11391
        $em = Database::getManager();
11392
        $item = new CLpCategory();
11393
        $item->setName($params['name']);
11394
        $item->setCId($params['c_id']);
11395
        $em->persist($item);
11396
        $em->flush();
11397
    }
11398
    /**
11399
     * @param array $params
11400
     */
11401
    public static function updateCategory($params)
11402
    {
11403
        $em = Database::getManager();
11404
        /** @var CLpCategory $item */
11405
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11406
        if ($item) {
11407
            $item->setName($params['name']);
11408
            $em->merge($item);
11409
            $em->flush();
11410
        }
11411
    }
11412
11413
    /**
11414
     * @param int $id
11415
     */
11416
    public static function moveUpCategory($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 $id
11432
     */
11433
    public static function moveDownCategory($id)
11434
    {
11435
        $id = (int) $id;
11436
        $em = Database::getManager();
11437
        /** @var CLpCategory $item */
11438
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11439
        if ($item) {
11440
            $position = $item->getPosition() + 1;
11441
            $item->setPosition($position);
11442
            $em->persist($item);
11443
            $em->flush();
11444
        }
11445
    }
11446
11447
    /**
11448
     * @param int $courseId
11449
     * @return int|mixed
11450
     */
11451
    public static function getCountCategories($courseId)
11452
    {
11453
        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...
11454
            return 0;
11455
        }
11456
        $em = Database::getManager();
11457
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11458
        $query->setParameter('id', $courseId);
11459
11460
        return $query->getSingleScalarResult();
11461
    }
11462
11463
    /**
11464
     * @param int $courseId
11465
     *
11466
     * @return mixed
11467
     */
11468
    public static function getCategories($courseId)
11469
    {
11470
        $em = Database::getManager();
11471
        //Default behaviour
11472
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11473
            array('cId' => $course_id),
11474
            array('name' => 'ASC')
11475
        );*/
11476
11477
        // Using doctrine extensions
11478
        /** @var SortableRepository $repo */
11479
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11480
        $items = $repo
11481
            ->getBySortableGroupsQuery(['cId' => $courseId])
11482
            ->getResult();
11483
11484
        return $items;
11485
    }
11486
11487
    /**
11488
     * @param int $id
11489
     *
11490
     * @return CLpCategory
11491
     */
11492
    public static function getCategory($id)
11493
    {
11494
        $id = (int) $id;
11495
        $em = Database::getManager();
11496
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11497
11498
        return $item;
11499
    }
11500
11501
    /**
11502
     * @param int $courseId
11503
     * @return array
11504
     */
11505
    public static function getCategoryByCourse($courseId)
11506
    {
11507
        $em = Database::getManager();
11508
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11509
            ['cId' => $courseId]
11510
        );
11511
11512
        return $items;
11513
    }
11514
11515
    /**
11516
     * @param int $id
11517
     *
11518
     * @return mixed
11519
     */
11520
    public static function deleteCategory($id)
11521
    {
11522
        $em = Database::getManager();
11523
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11524
        if ($item) {
11525
            $courseId = $item->getCId();
11526
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11527
            $query->setParameter('id', $courseId);
11528
            $query->setParameter('catId', $item->getId());
11529
            $lps = $query->getResult();
11530
11531
            // Setting category = 0.
11532
            if ($lps) {
11533
                foreach ($lps as $lpItem) {
11534
                    $lpItem->setCategoryId(0);
11535
                }
11536
            }
11537
11538
            // Removing category.
11539
            $em->remove($item);
11540
            $em->flush();
11541
11542
            $courseInfo = api_get_course_info_by_id($courseId);
11543
            $sessionId = api_get_session_id();
11544
11545
            // Delete link tool
11546
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11547
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11548
            // Delete tools
11549
            $sql = "DELETE FROM $tbl_tool
11550
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11551
            Database::query($sql);
11552
        }
11553
    }
11554
11555
    /**
11556
     * @param int $courseId
11557
     * @param bool $addSelectOption
11558
     *
11559
     * @return mixed
11560
     */
11561
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
11562
    {
11563
        $items = self::getCategoryByCourse($courseId);
11564
        $cats = [];
11565
        if ($addSelectOption) {
11566
            $cats = [get_lang('SelectACategory')];
11567
        }
11568
11569
        if (!empty($items)) {
11570
            foreach ($items as $cat) {
11571
                $cats[$cat->getId()] = $cat->getName();
11572
            }
11573
        }
11574
11575
        return $cats;
11576
    }
11577
11578
    /**
11579
     * Return the scorm item type object with spaces replaced with _
11580
     * The return result is use to build a css classname like scorm_type_$return
11581
     * @param $in_type
11582
     * @return mixed
11583
     */
11584
    private static function format_scorm_type_item($in_type)
11585
    {
11586
        return str_replace(' ', '_', $in_type);
11587
    }
11588
11589
    /**
11590
     * @param string $courseCode
11591
     * @param int $lp_id
11592
     * @param int $user_id
11593
     *
11594
     * @return learnpath
11595
     */
11596
    public static function getLpFromSession($courseCode, $lp_id, $user_id)
11597
    {
11598
        $learnPath = null;
11599
        $lpObject = Session::read('lpobject');
11600
        if ($lpObject !== null) {
11601
            $learnPath = unserialize($lpObject);
11602
        }
11603
11604
        if (!is_object($learnPath)) {
11605
            $learnPath = new learnpath($courseCode, $lp_id, $user_id);
11606
        }
11607
11608
        return $learnPath;
11609
    }
11610
11611
    /**
11612
     * @param int $itemId
11613
     * @return learnpathItem|false
11614
     */
11615
    public function getItem($itemId)
11616
    {
11617
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
11618
            return $this->items[$itemId];
11619
        }
11620
11621
        return false;
11622
    }
11623
11624
    /**
11625
     * @return int
11626
     */
11627
    public function getCategoryId()
11628
    {
11629
        return $this->categoryId;
11630
    }
11631
11632
    /**
11633
     * @param int $categoryId
11634
     * @return bool
11635
     */
11636
    public function setCategoryId($categoryId)
11637
    {
11638
        $this->categoryId = intval($categoryId);
11639
        $courseId = api_get_course_int_id();
11640
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11641
        $lp_id = $this->get_id();
11642
        $sql = "UPDATE $lp_table SET category_id = ".$this->categoryId."
11643
                WHERE iid = $lp_id";
11644
        Database::query($sql);
11645
11646
        return true;
11647
    }
11648
11649
    /**
11650
     * Get whether this is a learning path with the possibility to subscribe
11651
     * users or not
11652
     * @return int
11653
     */
11654
    public function getSubscribeUsers()
11655
    {
11656
        return $this->subscribeUsers;
11657
    }
11658
11659
    /**
11660
     * Set whether this is a learning path with the possibility to subscribe
11661
     * users or not
11662
     * @param int $value (0 = false, 1 = true)
11663
     * @return bool
11664
     */
11665
    public function setSubscribeUsers($value)
11666
    {
11667
        if ($this->debug > 0) {
11668
            error_log('New LP - In learnpath::set_subscribe_users()', 0);
11669
        }
11670
        $this->subscribeUsers = (int) $value;
11671
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11672
        $lp_id = $this->get_id();
11673
        $sql = "UPDATE $lp_table SET subscribe_users = ".$this->subscribeUsers."
11674
                WHERE iid = $lp_id";
11675
        Database::query($sql);
11676
11677
        return true;
11678
    }
11679
11680
    /**
11681
     * Calculate the count of stars for a user in this LP
11682
     * This calculation is based on the following rules:
11683
     * - the student gets one star when he gets to 50% of the learning path
11684
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
11685
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
11686
     * - the student gets the final star when the score for the *last* test is >= 80%
11687
     * @param int $sessionId Optional. The session ID
11688
     * @return int The count of stars
11689
     */
11690
    public function getCalculateStars($sessionId = 0)
11691
    {
11692
        $stars = 0;
11693
        $progress = self::getProgress(
11694
            $this->lp_id,
11695
            $this->user_id,
11696
            $this->course_int_id,
11697
            $sessionId
11698
        );
11699
11700
        if ($progress >= 50) {
11701
            $stars++;
11702
        }
11703
11704
        // Calculate stars chapters evaluation
11705
        $exercisesItems = $this->getExercisesItems();
11706
11707
        if (!empty($exercisesItems)) {
11708
            $totalResult = 0;
11709
11710
            foreach ($exercisesItems as $exerciseItem) {
11711
                $exerciseResultInfo = Event::getExerciseResultsByUser(
11712
                    $this->user_id,
11713
                    $exerciseItem->path,
11714
                    $this->course_int_id,
11715
                    $sessionId,
11716
                    $this->lp_id,
11717
                    $exerciseItem->db_id
11718
                );
11719
11720
                $exerciseResultInfo = end($exerciseResultInfo);
11721
11722
                if (!$exerciseResultInfo) {
11723
                    continue;
11724
                }
11725
11726
                if (!empty($exerciseResultInfo['exe_weighting'])) {
11727
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
11728
                } else {
11729
                    $exerciseResult = 0;
11730
                }
11731
                $totalResult += $exerciseResult;
11732
            }
11733
11734
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
11735
11736
            if ($totalExerciseAverage >= 50) {
11737
                $stars++;
11738
            }
11739
11740
            if ($totalExerciseAverage >= 80) {
11741
                $stars++;
11742
            }
11743
        }
11744
11745
        // Calculate star for final evaluation
11746
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11747
11748
        if (!empty($finalEvaluationItem)) {
11749
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11750
                $this->user_id,
11751
                $finalEvaluationItem->path,
11752
                $this->course_int_id,
11753
                $sessionId,
11754
                $this->lp_id,
11755
                $finalEvaluationItem->db_id
11756
            );
11757
11758
            $evaluationResultInfo = end($evaluationResultInfo);
11759
11760
            if ($evaluationResultInfo) {
11761
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
11762
11763
                if ($evaluationResult >= 80) {
11764
                    $stars++;
11765
                }
11766
            }
11767
        }
11768
11769
        return $stars;
11770
    }
11771
11772
    /**
11773
     * Get the items of exercise type
11774
     * @return array The items. Otherwise return false
11775
     */
11776
    public function getExercisesItems()
11777
    {
11778
        $exercises = [];
11779
        foreach ($this->items as $item) {
11780
            if ($item->type != 'quiz') {
11781
                continue;
11782
            }
11783
            $exercises[] = $item;
11784
        }
11785
11786
        array_pop($exercises);
11787
11788
        return $exercises;
11789
    }
11790
11791
    /**
11792
     * Get the item of exercise type (evaluation type)
11793
     * @return array The final evaluation. Otherwise return false
11794
     */
11795
    public function getFinalEvaluationItem()
11796
    {
11797
        $exercises = [];
11798
        foreach ($this->items as $item) {
11799
            if ($item->type != 'quiz') {
11800
                continue;
11801
            }
11802
11803
            $exercises[] = $item;
11804
        }
11805
11806
        return array_pop($exercises);
11807
    }
11808
11809
    /**
11810
     * Calculate the total points achieved for the current user in this learning path
11811
     * @param int $sessionId Optional. The session Id
11812
     * @return int
11813
     */
11814
    public function getCalculateScore($sessionId = 0)
11815
    {
11816
        // Calculate stars chapters evaluation
11817
        $exercisesItems = $this->getExercisesItems();
11818
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11819
        $totalExercisesResult = 0;
11820
        $totalEvaluationResult = 0;
11821
11822
        if ($exercisesItems !== false) {
11823
            foreach ($exercisesItems as $exerciseItem) {
11824
                $exerciseResultInfo = Event::getExerciseResultsByUser(
11825
                    $this->user_id,
11826
                    $exerciseItem->path,
11827
                    $this->course_int_id,
11828
                    $sessionId,
11829
                    $this->lp_id,
11830
                    $exerciseItem->db_id
11831
                );
11832
11833
                $exerciseResultInfo = end($exerciseResultInfo);
11834
11835
                if (!$exerciseResultInfo) {
11836
                    continue;
11837
                }
11838
11839
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
11840
            }
11841
        }
11842
11843
        if (!empty($finalEvaluationItem)) {
11844
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11845
                $this->user_id,
11846
                $finalEvaluationItem->path,
11847
                $this->course_int_id,
11848
                $sessionId,
11849
                $this->lp_id,
11850
                $finalEvaluationItem->db_id
11851
            );
11852
11853
            $evaluationResultInfo = end($evaluationResultInfo);
11854
11855
            if ($evaluationResultInfo) {
11856
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
11857
            }
11858
        }
11859
11860
        return $totalExercisesResult + $totalEvaluationResult;
11861
    }
11862
11863
    /**
11864
     * Check if URL is not allowed to be show in a iframe
11865
     * @param string $src
11866
     *
11867
     * @return string
11868
     */
11869
    public function fixBlockedLinks($src)
11870
    {
11871
        $urlInfo = parse_url($src);
11872
        $platformProtocol = 'https';
11873
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
11874
            $platformProtocol = 'http';
11875
        }
11876
11877
        $protocolFixApplied = false;
11878
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
11879
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
11880
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
11881
11882
        if ($platformProtocol != $scheme) {
11883
            Session::write('x_frame_source', $src);
11884
            $src = 'blank.php?error=x_frames_options';
11885
            $protocolFixApplied = true;
11886
        }
11887
11888
        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...
11889
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
11890
                // Check X-Frame-Options
11891
                $ch = curl_init();
11892
11893
                $options = [
11894
                    CURLOPT_URL => $src,
11895
                    CURLOPT_RETURNTRANSFER => true,
11896
                    CURLOPT_HEADER => true,
11897
                    CURLOPT_FOLLOWLOCATION => true,
11898
                    CURLOPT_ENCODING => "",
11899
                    CURLOPT_AUTOREFERER => true,
11900
                    CURLOPT_CONNECTTIMEOUT => 120,
11901
                    CURLOPT_TIMEOUT => 120,
11902
                    CURLOPT_MAXREDIRS => 10,
11903
                ];
11904
                curl_setopt_array($ch, $options);
11905
                $response = curl_exec($ch);
11906
                $httpCode = curl_getinfo($ch);
11907
                $headers = substr($response, 0, $httpCode['header_size']);
11908
11909
                $error = false;
11910
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
11911
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
11912
                ) {
11913
                    $error = true;
11914
                }
11915
11916
                if ($error) {
11917
                    Session::write('x_frame_source', $src);
11918
                    $src = 'blank.php?error=x_frames_options';
11919
                }
11920
            }
11921
        }
11922
11923
        return $src;
11924
    }
11925
11926
    /**
11927
     * Check if this LP has a created forum in the basis course
11928
     * @return boolean
11929
     */
11930
    public function lpHasForum()
11931
    {
11932
        $forumTable = Database::get_course_table(TABLE_FORUM);
11933
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
11934
11935
        $fakeFrom = "
11936
            $forumTable f
11937
            INNER JOIN $itemProperty ip
11938
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
11939
        ";
11940
11941
        $resultData = Database::select(
11942
            'COUNT(f.iid) AS qty',
11943
            $fakeFrom,
11944
            [
11945
                'where' => [
11946
                    'ip.visibility != ? AND ' => 2,
11947
                    'ip.tool = ? AND ' => TOOL_FORUM,
11948
                    'f.c_id = ? AND ' => intval($this->course_int_id),
11949
                    'f.lp_id = ?' => intval($this->lp_id)
11950
                ]
11951
            ],
11952
            'first'
11953
        );
11954
11955
        return $resultData['qty'] > 0;
11956
    }
11957
11958
    /**
11959
     * Get the forum for this learning path
11960
     * @param int $sessionId
11961
     * @return boolean
11962
     */
11963
    public function getForum($sessionId = 0)
11964
    {
11965
        $forumTable = Database::get_course_table(TABLE_FORUM);
11966
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
11967
11968
        $fakeFrom = "$forumTable f
11969
            INNER JOIN $itemProperty ip ";
11970
11971
        if ($this->lp_session_id == 0) {
11972
            $fakeFrom .= "
11973
                ON (
11974
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
11975
                        f.session_id = ip.session_id OR ip.session_id IS NULL
11976
                    )
11977
                )
11978
            ";
11979
        } else {
11980
            $fakeFrom .= "
11981
                ON (
11982
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
11983
                )
11984
            ";
11985
        }
11986
11987
        $resultData = Database::select(
11988
            'f.*',
11989
            $fakeFrom,
11990
            [
11991
                'where' => [
11992
                    'ip.visibility != ? AND ' => 2,
11993
                    'ip.tool = ? AND ' => TOOL_FORUM,
11994
                    'f.session_id = ? AND ' => $sessionId,
11995
                    'f.c_id = ? AND ' => intval($this->course_int_id),
11996
                    'f.lp_id = ?' => intval($this->lp_id)
11997
                ]
11998
            ],
11999
            'first'
12000
        );
12001
12002
        if (empty($resultData)) {
12003
            return false;
12004
        }
12005
12006
        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...
12007
    }
12008
12009
    /**
12010
     * Create a forum for this learning path
12011
     * @param int $forumCategoryId
12012
     * @return int The forum ID if was created. Otherwise return false
12013
     */
12014
    public function createForum($forumCategoryId)
12015
    {
12016
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12017
12018
        $forumId = store_forum(
12019
            [
12020
                'lp_id' => $this->lp_id,
12021
                'forum_title' => $this->name,
12022
                'forum_comment' => null,
12023
                'forum_category' => intval($forumCategoryId),
12024
                'students_can_edit_group' => ['students_can_edit' => 0],
12025
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12026
                'default_view_type_group' => ['default_view_type' => 'flat'],
12027
                'group_forum' => 0,
12028
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public']
12029
            ],
12030
            [],
12031
            true
12032
        );
12033
12034
        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...
12035
    }
12036
12037
    /**
12038
     * Check and obtain the lp final item if exist
12039
     *
12040
     * @return learnpathItem
12041
     */
12042
    private function getFinalItem()
12043
    {
12044
        if (empty($this->items)) {
12045
            return null;
12046
        }
12047
12048
        foreach ($this->items as $item) {
12049
            if ($item->type !== 'final_item') {
12050
                continue;
12051
            }
12052
12053
            return $item;
12054
        }
12055
    }
12056
12057
    /**
12058
     * Get the LP Final Item Template
12059
     *
12060
     * @return string
12061
     */
12062
    private function getFinalItemTemplate()
12063
    {
12064
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
12065
    }
12066
12067
    /**
12068
     * Get the LP Final Item Url
12069
     *
12070
     * @return string
12071
     */
12072
    private function getSavedFinalItem()
12073
    {
12074
        $finalItem = $this->getFinalItem();
12075
        $doc = DocumentManager::get_document_data_by_id(
12076
            $finalItem->path,
12077
            $this->cc
12078
        );
12079
        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...
12080
            return file_get_contents($doc['absolute_path']);
12081
        }
12082
12083
        return '';
12084
    }
12085
12086
    /**
12087
     * Get the LP Final Item form
12088
     *
12089
     * @return string
12090
     */
12091
    public function getFinalItemForm()
12092
    {
12093
        $finalItem = $this->getFinalItem();
12094
        $title = '';
12095
12096
        if ($finalItem) {
12097
            $title = $finalItem->get_title();
12098
            $buttonText = get_lang('Save');
12099
            $content = $this->getSavedFinalItem();
12100
        } else {
12101
            $buttonText = get_lang('LPCreateDocument');
12102
            $content = $this->getFinalItemTemplate();
12103
        }
12104
12105
        $courseInfo = api_get_course_info();
12106
        $result = $this->generate_lp_folder($courseInfo);
12107
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12108
        $relative_prefix = '../../';
12109
12110
        $editorConfig = [
12111
            'ToolbarSet' => 'LearningPathDocuments',
12112
            'Width' => '100%',
12113
            'Height' => '500',
12114
            'FullPage' => true,
12115
            'CreateDocumentDir' => $relative_prefix,
12116
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12117
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path
12118
        ];
12119
12120
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12121
            'type' => 'document',
12122
            'lp_id' => $this->lp_id
12123
        ]);
12124
12125
        $form = new FormValidator('final_item', 'POST', $url);
12126
        $form->addText('title', get_lang('Title'));
12127
        $form->addButtonSave($buttonText);
12128
        $form->addHtml(
12129
            Display::return_message(
12130
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12131
                'normal',
12132
                false
12133
            )
12134
        );
12135
12136
        $renderer = $form->defaultRenderer();
12137
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12138
12139
        $form->addHtmlEditor(
12140
            'content_lp_certificate',
12141
            null,
12142
            true,
12143
            false,
12144
            $editorConfig,
12145
            true
12146
        );
12147
        $form->addHidden('action', 'add_final_item');
12148
        $form->addHidden('path', Session::read('pathItem'));
12149
        $form->addHidden('previous', $this->get_last());
12150
        $form->setDefaults(
12151
            ['title' => $title, 'content_lp_certificate' => $content]
12152
        );
12153
12154
        if ($form->validate()) {
12155
            $values = $form->exportValues();
12156
            $lastItemId = $this->get_last();
12157
12158
            if (!$finalItem) {
12159
                $documentId = $this->create_document(
12160
                    $this->course_info,
12161
                    $values['content_lp_certificate'],
12162
                    $values['title']
12163
                );
12164
                $this->add_item(
12165
                    0,
12166
                    $lastItemId,
12167
                    'final_item',
12168
                    $documentId,
12169
                    $values['title'],
12170
                    ''
12171
                );
12172
12173
                Display::addFlash(
12174
                    Display::return_message(get_lang('Added'))
12175
                );
12176
            } else {
12177
                $this->edit_document($this->course_info);
12178
            }
12179
        }
12180
12181
        return $form->returnForm();
12182
    }
12183
12184
    /**
12185
     * Check if the current lp item is first, both, last or none from lp list
12186
     *
12187
     * @param int $currentItemId
12188
     * @return string
12189
     */
12190
    public function isFirstOrLastItem($currentItemId)
12191
    {
12192
        if ($this->debug > 0) {
12193
            error_log('New LP - In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12194
        }
12195
12196
        $lpItemId = [];
12197
        $typeListNotToVerify = self::getChapterTypes();
12198
12199
        // Using get_toc() function instead $this->items because returns the correct order of the items
12200
        foreach ($this->get_toc() as $item) {
12201
            if (!in_array($item['type'], $typeListNotToVerify)) {
12202
                $lpItemId[] = $item['id'];
12203
            }
12204
        }
12205
12206
        $lastLpItemIndex = count($lpItemId) - 1;
12207
        $position = array_search($currentItemId, $lpItemId);
12208
12209
        switch ($position) {
12210
            case 0:
12211
                if (!$lastLpItemIndex) {
12212
                    $answer = 'both';
12213
                    break;
12214
                }
12215
12216
                $answer = 'first';
12217
                break;
12218
            case $lastLpItemIndex:
12219
                $answer = 'last';
12220
                break;
12221
            default:
12222
                $answer = 'none';
12223
        }
12224
12225
        return $answer;
12226
    }
12227
12228
    /**
12229
     * Get whether this is a learning path with the accumulated SCORM time or not
12230
     * @return int
12231
     */
12232
    public function getAccumulateScormTime()
12233
    {
12234
        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...
12235
    }
12236
12237
    /**
12238
     * Set whether this is a learning path with the accumulated SCORM time or not
12239
     * @param int $value (0 = false, 1 = true)
12240
     * @return bool Always returns true
12241
     */
12242
    public function setAccumulateScormTime($value)
12243
    {
12244
        if ($this->debug > 0) {
12245
            error_log('New LP - In learnpath::setAccumulateScormTime()', 0);
12246
        }
12247
        $this->accumulateScormTime = intval($value);
12248
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12249
        $lp_id = $this->get_id();
12250
        $sql = "UPDATE $lp_table 
12251
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12252
                WHERE iid = $lp_id";
12253
        Database::query($sql);
12254
12255
        return true;
12256
    }
12257
12258
    /**
12259
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12260
     * the new learning path tool.
12261
     *
12262
     * The function is a big switch on tool type.
12263
     * In each case, we query the corresponding table for information and build the link
12264
     * with that information.
12265
     * @author Yannick Warnier <[email protected]> - rebranding based on
12266
     * previous work (display_addedresource_link_in_learnpath())
12267
     * @param int	$course_id Course code
12268
     * @param int $learningPathId The learning path ID (in lp table)
12269
     * @param int $id_in_path the unique index in the items table
12270
     * @param int $lpViewId
12271
     * @param string $origin
12272
     * @return string
12273
     */
12274
    public static function rl_get_resource_link_for_learnpath(
12275
        $course_id,
12276
        $learningPathId,
12277
        $id_in_path,
12278
        $lpViewId,
12279
        $origin = 'learnpath'
12280
    ) {
12281
        $session_id = api_get_session_id();
12282
        $course_info = api_get_course_info_by_id($course_id);
12283
12284
        $learningPathId = intval($learningPathId);
12285
        $id_in_path = intval($id_in_path);
12286
        $lpViewId = intval($lpViewId);
12287
12288
        $em = Database::getManager();
12289
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12290
        /** @var CLpItem $rowItem */
12291
        $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...
12292
            'cId' => $course_id,
12293
            'lpId' => $learningPathId,
12294
            'id' => $id_in_path
12295
        ]);
12296
12297
        if (!$rowItem) {
12298
            return -1;
12299
        }
12300
12301
        $course_code = $course_info['code'];
12302
        $type = $rowItem->getItemType();
12303
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12304
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12305
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12306
        $link = '';
12307
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12308
        switch ($type) {
12309
            case 'dir':
12310
                return $main_dir_path.'lp/blank.php';
12311
            case TOOL_CALENDAR_EVENT:
12312
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12313
            case TOOL_ANNOUNCEMENT:
12314
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12315
            case TOOL_LINK:
12316
                $linkInfo = Link::getLinkInfo($id);
12317
                if (isset($linkInfo['url'])) {
12318
                    return $linkInfo['url'];
12319
                }
12320
                return '';
12321
            case TOOL_QUIZ:
12322
                if (empty($id)) {
12323
                    return '';
12324
                }
12325
12326
                // Get the lp_item_view with the highest view_count.
12327
                $learnpathItemViewResult = $em
12328
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12329
                    ->findBy(
12330
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12331
                        ['viewCount' => 'DESC'],
12332
                        1
12333
                    );
12334
                /** @var CLpItemView $learnpathItemViewData */
12335
                $learnpathItemViewData = current($learnpathItemViewResult);
12336
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12337
12338
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12339
                    .http_build_query([
12340
                        'lp_init' => 1,
12341
                        'learnpath_item_view_id' => $learnpathItemViewId,
12342
                        'learnpath_id' => $learningPathId,
12343
                        'learnpath_item_id' => $id_in_path,
12344
                        'exerciseId' => $id
12345
                    ]);
12346
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12347
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12348
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12349
                $myrow = Database::fetch_array($result);
12350
                $path = $myrow['path'];
12351
12352
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12353
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12354
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12355
            case TOOL_FORUM:
12356
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12357
            case TOOL_THREAD:  //forum post
12358
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12359
                if (empty($id)) {
12360
                    return '';
12361
                }
12362
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12363
                $result = Database::query($sql);
12364
                $myrow = Database::fetch_array($result);
12365
12366
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12367
                    .$extraParams;
12368
            case TOOL_POST:
12369
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12370
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12371
                $myrow = Database::fetch_array($result);
12372
12373
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12374
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12375
            case TOOL_DOCUMENT:
12376
                $document = $em
12377
                    ->getRepository('ChamiloCourseBundle:CDocument')
12378
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12379
12380
                if (!$document) {
12381
                    return '';
12382
                }
12383
12384
                $documentPathInfo = pathinfo($document->getPath());
12385
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12386
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12387
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12388
12389
                $openmethod = 2;
12390
                $officedoc = false;
12391
                Session::write('openmethod', $openmethod);
12392
                Session::write('officedoc', $officedoc);
12393
12394
                if ($showDirectUrl) {
12395
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12396
                }
12397
12398
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12399
            case TOOL_LP_FINAL_ITEM:
12400
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12401
                    .$extraParams;
12402
            case 'assignments':
12403
                return $main_dir_path.'work/work.php?'.$extraParams;
12404
            case TOOL_DROPBOX:
12405
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12406
            case 'introduction_text': //DEPRECATED
12407
                return '';
12408
            case TOOL_COURSE_DESCRIPTION:
12409
                return $main_dir_path.'course_description?'.$extraParams;
12410
            case TOOL_GROUP:
12411
                return $main_dir_path.'group/group.php?'.$extraParams;
12412
            case TOOL_USER:
12413
                return $main_dir_path.'user/user.php?'.$extraParams;
12414
            case TOOL_STUDENTPUBLICATION:
12415
                if (!empty($rowItem->getPath())) {
12416
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12417
                }
12418
12419
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12420
        } //end switch
12421
12422
        return $link;
12423
    }
12424
12425
    /**
12426
     * Gets the name of a resource (generally used in learnpath when no name is provided)
12427
     *
12428
     * @author Yannick Warnier <[email protected]>
12429
     * @param string    Course code
12430
     * @param string    The tool type (using constants declared in main_api.lib.php)
12431
     * @param integer    The resource ID
12432
     * @return string
12433
     */
12434
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12435
    {
12436
        $_course = api_get_course_info($course_code);
12437
        $course_id = $_course['real_id'];
12438
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12439
        $learningPathId = intval($learningPathId);
12440
        $id_in_path = intval($id_in_path);
12441
12442
        $sql_item = "SELECT item_type, title, ref FROM $tbl_lp_item
12443
                     WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12444
        $res_item = Database::query($sql_item);
12445
12446
        if (Database::num_rows($res_item) < 1) {
12447
            return '';
12448
        }
12449
        $row_item = Database::fetch_array($res_item);
12450
        $type = strtolower($row_item['item_type']);
12451
        $id = $row_item['ref'];
12452
        $output = '';
12453
12454
        switch ($type) {
12455
            case TOOL_CALENDAR_EVENT:
12456
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12457
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12458
                $myrow = Database::fetch_array($result);
12459
                $output = $myrow['title'];
12460
                break;
12461
            case TOOL_ANNOUNCEMENT:
12462
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12463
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12464
                $myrow = Database::fetch_array($result);
12465
                $output = $myrow['title'];
12466
                break;
12467
            case TOOL_LINK:
12468
                // Doesn't take $target into account.
12469
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12470
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12471
                $myrow = Database::fetch_array($result);
12472
                $output = $myrow['title'];
12473
                break;
12474
            case TOOL_QUIZ:
12475
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12476
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id=$id");
12477
                $myrow = Database::fetch_array($result);
12478
                $output = $myrow['title'];
12479
                break;
12480
            case TOOL_FORUM:
12481
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12482
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id=$id");
12483
                $myrow = Database::fetch_array($result);
12484
                $output = $myrow['forum_name'];
12485
                break;
12486
            case TOOL_THREAD:  //=topics
12487
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12488
                // Grabbing the title of the post.
12489
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12490
                $result_title = Database::query($sql_title);
12491
                $myrow_title = Database::fetch_array($result_title);
12492
                $output = $myrow_title['post_title'];
12493
                break;
12494
            case TOOL_POST:
12495
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12496
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
12497
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12498
                $result = Database::query($sql);
12499
                $post = Database::fetch_array($result);
12500
                $output = $post['post_title'];
12501
                break;
12502
            case 'dir':
12503
                $title = $row_item['title'];
12504
                if (!empty($title)) {
12505
                    $output = $title;
12506
                } else {
12507
                    $output = '-';
12508
                }
12509
                break;
12510
            case TOOL_DOCUMENT:
12511
                $title = $row_item['title'];
12512
                if (!empty($title)) {
12513
                    $output = $title;
12514
                } else {
12515
                    $output = '-';
12516
                }
12517
                break;
12518
            case 'hotpotatoes':
12519
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12520
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12521
                $myrow = Database::fetch_array($result);
12522
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12523
                $last = count($pathname) - 1; // Making a correct name for the link.
12524
                $filename = $pathname[$last]; // Making a correct name for the link.
12525
                $ext = explode('.', $filename);
12526
                $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

12526
                $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...
12527
                $myrow['path'] = rawurlencode($myrow['path']);
12528
                $output = $filename;
12529
                break;
12530
        }
12531
        return stripslashes($output);
12532
    }
12533
12534
    /**
12535
     * Get the parent names for the current item
12536
     * @param int $newItemId Optional. The item ID
12537
     * @return array
12538
     */
12539
    public function getCurrentItemParentNames($newItemId = 0)
12540
    {
12541
        $newItemId = $newItemId ?: $this->get_current_item_id();
12542
        $return = [];
12543
        $item = $this->getItem($newItemId);
12544
        $parent = $this->getItem($item->get_parent());
12545
12546
        while ($parent) {
12547
            $return[] = $parent->get_title();
12548
12549
            $parent = $this->getItem($parent->get_parent());
12550
        }
12551
12552
        return array_reverse($return);
12553
    }
12554
12555
    /**
12556
     * Reads and process "lp_subscription_settings" setting
12557
     * @return array
12558
     */
12559
    public static function getSubscriptionSettings()
12560
    {
12561
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
12562
        if (empty($subscriptionSettings)) {
12563
            // By default allow both settings
12564
            $subscriptionSettings = [
12565
                'allow_add_users_to_lp' => true,
12566
                'allow_add_users_to_lp_category' => true,
12567
            ];
12568
        } else {
12569
            $subscriptionSettings = $subscriptionSettings['options'];
12570
        }
12571
12572
        return $subscriptionSettings;
12573
    }
12574
}
12575