1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* For licensing terms, see /license.txt */ |
4
|
|
|
|
5
|
|
|
use Chamilo\CoreBundle\Entity\Course; |
6
|
|
|
use Chamilo\CoreBundle\Entity\ResourceLink; |
7
|
|
|
use Chamilo\CoreBundle\Entity\TrackEExercise; |
8
|
|
|
use Chamilo\CoreBundle\Entity\User; |
9
|
|
|
use Chamilo\CoreBundle\Entity\Session as SessionEntity; |
10
|
|
|
use Chamilo\CoreBundle\Event\Events; |
11
|
|
|
use Chamilo\CoreBundle\Event\LearningPathEndedEvent; |
12
|
|
|
use Chamilo\CoreBundle\ServiceHelper\ThemeHelper; |
13
|
|
|
use Chamilo\CourseBundle\Entity\CLpRelUser; |
14
|
|
|
use Chamilo\CoreBundle\Framework\Container; |
15
|
|
|
use Chamilo\CoreBundle\Repository\Node\CourseRepository; |
16
|
|
|
use Chamilo\CourseBundle\Entity\CSurvey; |
17
|
|
|
use Chamilo\CourseBundle\Repository\CLpRelUserRepository; |
18
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver; |
19
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder; |
20
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer; |
21
|
|
|
use Chamilo\CourseBundle\Entity\CDocument; |
22
|
|
|
use Chamilo\CourseBundle\Entity\CForumCategory; |
23
|
|
|
use Chamilo\CourseBundle\Entity\CForumThread; |
24
|
|
|
use Chamilo\CourseBundle\Entity\CLink; |
25
|
|
|
use Chamilo\CourseBundle\Entity\CLp; |
26
|
|
|
use Chamilo\CourseBundle\Entity\CLpCategory; |
27
|
|
|
use Chamilo\CourseBundle\Entity\CLpItem; |
28
|
|
|
use Chamilo\CourseBundle\Entity\CLpItemView; |
29
|
|
|
use Chamilo\CourseBundle\Entity\CQuiz; |
30
|
|
|
use Chamilo\CourseBundle\Entity\CStudentPublication; |
31
|
|
|
use Chamilo\CourseBundle\Entity\CTool; |
32
|
|
|
use \Chamilo\CoreBundle\Entity\ResourceNode; |
33
|
|
|
use ChamiloSession as Session; |
34
|
|
|
use Doctrine\Common\Collections\Criteria; |
35
|
|
|
use PhpZip\ZipFile; |
36
|
|
|
use Symfony\Component\Finder\Finder; |
37
|
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
38
|
|
|
use Chamilo\CoreBundle\Component\Utils\ObjectIcon; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Class learnpath |
42
|
|
|
* This class defines the parent attributes and methods for Chamilo learnpaths |
43
|
|
|
* and SCORM learnpaths. It is used by the scorm class. |
44
|
|
|
* |
45
|
|
|
* @todo decouple class |
46
|
|
|
* |
47
|
|
|
* @author Yannick Warnier <[email protected]> |
48
|
|
|
* @author Julio Montoya <[email protected]> Several improvements and fixes |
49
|
|
|
*/ |
50
|
|
|
class learnpath |
51
|
|
|
{ |
52
|
|
|
public const MAX_LP_ITEM_TITLE_LENGTH = 36; |
53
|
|
|
public const STATUS_CSS_CLASS_NAME = [ |
54
|
|
|
'not attempted' => 'scorm_not_attempted', |
55
|
|
|
'incomplete' => 'scorm_not_attempted', |
56
|
|
|
'failed' => 'scorm_failed', |
57
|
|
|
'completed' => 'scorm_completed', |
58
|
|
|
'passed' => 'scorm_completed', |
59
|
|
|
'succeeded' => 'scorm_completed', |
60
|
|
|
'browsed' => 'scorm_completed', |
61
|
|
|
]; |
62
|
|
|
|
63
|
|
|
public $attempt = 0; // The number for the current ID view. |
64
|
|
|
public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ... |
65
|
|
|
public $current; // Id of the current item the user is viewing. |
66
|
|
|
public $current_score; // The score of the current item. |
67
|
|
|
public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet). |
68
|
|
|
public $current_time_stop; // The time the user closed this resource. |
69
|
|
|
public $default_status = 'not attempted'; |
70
|
|
|
public $encoding = 'UTF-8'; |
71
|
|
|
public $error = ''; |
72
|
|
|
public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue() |
73
|
|
|
public $index; // The index of the active learnpath_item in $ordered_items array. |
74
|
|
|
/** @var learnpathItem[] */ |
75
|
|
|
public $items = []; |
76
|
|
|
public $last; // item_id of last item viewed in the learning path. |
77
|
|
|
public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized. |
78
|
|
|
public $license; // Which license this course has been given - not used yet on 20060522. |
79
|
|
|
public $lp_id; // DB iid for this learnpath. |
80
|
|
|
public $lp_view_id; // DB ID for lp_view |
81
|
|
|
public $maker; // Which maker has conceived the content (ENI, Articulate, ...). |
82
|
|
|
public $message = ''; |
83
|
|
|
public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded). |
84
|
|
|
public $name; // Learnpath name (they generally have one). |
85
|
|
|
public $ordered_items = []; // List of the learnpath items in the order they are to be read. |
86
|
|
|
public $path = ''; // Path inside the scorm directory (if scorm). |
87
|
|
|
public $theme; // The current theme of the learning path. |
88
|
|
|
public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not |
89
|
|
|
public $accumulateWorkTime; // The min time of learnpath |
90
|
|
|
|
91
|
|
|
// Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1). |
92
|
|
|
public $prevent_reinit = 1; |
93
|
|
|
|
94
|
|
|
// Describes the mode of progress bar display. |
95
|
|
|
public $seriousgame_mode = 0; |
96
|
|
|
public $progress_bar_mode = '%'; |
97
|
|
|
|
98
|
|
|
// Percentage progress as saved in the db. |
99
|
|
|
public $progress_db = 0; |
100
|
|
|
public $proximity; // Wether the content is distant or local or unknown. |
101
|
|
|
public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match. |
102
|
|
|
// !!!This array (refs_list) is built differently depending on the nature of the LP. |
103
|
|
|
// If SCORM, uses ref, if Chamilo, uses id to keep a unique value. |
104
|
|
|
public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ... |
105
|
|
|
// TODO: Check if this type variable is useful here (instead of just in the controller script). |
106
|
|
|
public $user_id; //ID of the user that is viewing/using the course |
107
|
|
|
public $update_queue = []; |
108
|
|
|
public $scorm_debug = 0; |
109
|
|
|
public $arrMenu = []; // Array for the menu items. |
110
|
|
|
public $debug = 0; // Logging level. |
111
|
|
|
public $lp_session_id = 0; |
112
|
|
|
public $lp_view_session_id = 0; // The specific view might be bound to a session. |
113
|
|
|
public $prerequisite = 0; |
114
|
|
|
public $use_max_score = 1; // 1 or 0 |
115
|
|
|
public $subscribeUsers = 0; // Subscribe users or not |
116
|
|
|
public $created_on = ''; |
117
|
|
|
public $modified_on = ''; |
118
|
|
|
public $published_on = ''; |
119
|
|
|
public $expired_on = ''; |
120
|
|
|
public $ref; |
121
|
|
|
public $course_int_id; |
122
|
|
|
public $course_info; |
123
|
|
|
public $categoryId; |
124
|
|
|
public $scormUrl; |
125
|
|
|
public $entity; |
126
|
|
|
|
127
|
|
|
public function __construct(CLp $entity = null, $course_info, $user_id) |
128
|
|
|
{ |
129
|
|
|
$debug = $this->debug; |
130
|
|
|
$user_id = (int) $user_id; |
131
|
|
|
$this->encoding = api_get_system_encoding(); |
132
|
|
|
$lp_id = 0; |
133
|
|
|
if (null !== $entity) { |
134
|
|
|
$lp_id = $entity->getIid(); |
135
|
|
|
} |
136
|
|
|
$course_info = empty($course_info) ? api_get_course_info() : $course_info; |
137
|
|
|
$course_id = (int) $course_info['real_id']; |
138
|
|
|
$this->course_info = $course_info; |
139
|
|
|
$this->set_course_int_id($course_id); |
140
|
|
|
if (empty($lp_id) || empty($course_id)) { |
141
|
|
|
$this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'"; |
142
|
|
|
} else { |
143
|
|
|
//$this->entity = $entity; |
144
|
|
|
$this->lp_id = $lp_id; |
145
|
|
|
$this->type = $entity->getLpType(); |
146
|
|
|
$this->name = stripslashes($entity->getTitle()); |
147
|
|
|
$this->proximity = $entity->getContentLocal(); |
148
|
|
|
$this->theme = $entity->getTheme(); |
149
|
|
|
$this->maker = $entity->getContentLocal(); |
150
|
|
|
$this->prevent_reinit = $entity->getPreventReinit(); |
151
|
|
|
$this->seriousgame_mode = $entity->getSeriousgameMode(); |
152
|
|
|
$this->license = $entity->getContentLicense(); |
153
|
|
|
$this->scorm_debug = $entity->getDebug(); |
154
|
|
|
$this->js_lib = $entity->getJsLib(); |
155
|
|
|
$this->path = $entity->getPath(); |
156
|
|
|
$this->author = $entity->getAuthor(); |
157
|
|
|
$this->hide_toc_frame = $entity->getHideTocFrame(); |
158
|
|
|
//$this->lp_session_id = $entity->getSessionId(); |
159
|
|
|
$this->use_max_score = $entity->getUseMaxScore(); |
160
|
|
|
$this->subscribeUsers = $entity->getSubscribeUsers(); |
161
|
|
|
$this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s'); |
162
|
|
|
$this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s'); |
163
|
|
|
$this->ref = $entity->getRef(); |
164
|
|
|
$this->categoryId = 0; |
165
|
|
|
if ($entity->getCategory()) { |
166
|
|
|
$this->categoryId = $entity->getCategory()->getIid(); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if ($entity->hasAsset()) { |
170
|
|
|
$asset = $entity->getAsset(); |
171
|
|
|
$this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/'.$entity->getPath().'/'; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$this->accumulateScormTime = $entity->getAccumulateWorkTime(); |
175
|
|
|
|
176
|
|
|
if (!empty($entity->getPublishedOn())) { |
177
|
|
|
$this->published_on = $entity->getPublishedOn()->format('Y-m-d H:i:s'); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if (!empty($entity->getExpiredOn())) { |
181
|
|
|
$this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s'); |
182
|
|
|
} |
183
|
|
|
if (2 == $this->type) { |
184
|
|
|
if (1 == $entity->getForceCommit()) { |
185
|
|
|
$this->force_commit = true; |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
$this->mode = $entity->getDefaultViewMod(); |
189
|
|
|
|
190
|
|
|
// Check user ID. |
191
|
|
|
if (empty($user_id)) { |
192
|
|
|
$this->error = 'User ID is empty'; |
193
|
|
|
} else { |
194
|
|
|
$this->user_id = $user_id; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
// End of variables checking. |
198
|
|
|
$session_id = api_get_session_id(); |
199
|
|
|
// Get the session condition for learning paths of the base + session. |
200
|
|
|
$session = api_get_session_condition($session_id); |
201
|
|
|
// Now get the latest attempt from this user on this LP, if available, otherwise create a new one. |
202
|
|
|
$lp_table = Database::get_course_table(TABLE_LP_VIEW); |
203
|
|
|
|
204
|
|
|
// Selecting by view_count descending allows to get the highest view_count first. |
205
|
|
|
$sql = "SELECT * FROM $lp_table |
206
|
|
|
WHERE |
207
|
|
|
c_id = $course_id AND |
208
|
|
|
lp_id = $lp_id AND |
209
|
|
|
user_id = $user_id |
210
|
|
|
$session |
211
|
|
|
ORDER BY view_count DESC"; |
212
|
|
|
$res = Database::query($sql); |
213
|
|
|
|
214
|
|
|
if (Database::num_rows($res) > 0) { |
215
|
|
|
$row = Database::fetch_array($res); |
216
|
|
|
$this->attempt = $row['view_count']; |
217
|
|
|
$this->lp_view_id = $row['iid']; |
218
|
|
|
$this->last_item_seen = $row['last_item']; |
219
|
|
|
$this->progress_db = $row['progress']; |
220
|
|
|
$this->lp_view_session_id = $row['session_id']; |
221
|
|
|
} elseif (!api_is_invitee()) { |
222
|
|
|
$this->attempt = 1; |
223
|
|
|
$params = [ |
224
|
|
|
'c_id' => $course_id, |
225
|
|
|
'lp_id' => $lp_id, |
226
|
|
|
'user_id' => $user_id, |
227
|
|
|
'view_count' => 1, |
228
|
|
|
//'session_id' => $session_id, |
229
|
|
|
'last_item' => 0, |
230
|
|
|
]; |
231
|
|
|
if (!empty($session_id)) { |
232
|
|
|
$params['session_id'] = $session_id; |
233
|
|
|
} |
234
|
|
|
$this->last_item_seen = 0; |
235
|
|
|
$this->lp_view_session_id = $session_id; |
236
|
|
|
$this->lp_view_id = Database::insert($lp_table, $params); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$criteria = new Criteria(); |
240
|
|
|
$criteria |
241
|
|
|
->where($criteria->expr()->neq('path', 'root')) |
242
|
|
|
->orderBy( |
243
|
|
|
[ |
244
|
|
|
'parent' => Criteria::ASC, |
245
|
|
|
'displayOrder' => Criteria::ASC, |
246
|
|
|
] |
247
|
|
|
); |
248
|
|
|
$items = $entity->getItems()->matching($criteria); |
249
|
|
|
$lp_item_id_list = []; |
250
|
|
|
foreach ($items as $item) { |
251
|
|
|
$itemId = $item->getIid(); |
252
|
|
|
$lp_item_id_list[] = $itemId; |
253
|
|
|
|
254
|
|
|
switch ($this->type) { |
255
|
|
|
case CLp::AICC_TYPE: |
256
|
|
|
$oItem = new aiccItem('db', $itemId, $course_id); |
257
|
|
|
if (is_object($oItem)) { |
258
|
|
|
$oItem->set_lp_view($this->lp_view_id); |
259
|
|
|
$oItem->set_prevent_reinit($this->prevent_reinit); |
260
|
|
|
// Don't use reference here as the next loop will make the pointed object change. |
261
|
|
|
$this->items[$itemId] = $oItem; |
262
|
|
|
$this->refs_list[$oItem->ref] = $itemId; |
263
|
|
|
} |
264
|
|
|
break; |
265
|
|
|
case CLp::SCORM_TYPE: |
266
|
|
|
$oItem = new scormItem('db', $itemId); |
267
|
|
|
if (is_object($oItem)) { |
268
|
|
|
$oItem->set_lp_view($this->lp_view_id); |
269
|
|
|
$oItem->set_prevent_reinit($this->prevent_reinit); |
270
|
|
|
// Don't use reference here as the next loop will make the pointed object change. |
271
|
|
|
$this->items[$itemId] = $oItem; |
272
|
|
|
$this->refs_list[$oItem->ref] = $itemId; |
273
|
|
|
} |
274
|
|
|
break; |
275
|
|
|
case CLp::LP_TYPE: |
276
|
|
|
default: |
277
|
|
|
$oItem = new learnpathItem(null, $item); |
278
|
|
|
if (is_object($oItem)) { |
279
|
|
|
// Moved down to when we are sure the item_view exists. |
280
|
|
|
//$oItem->set_lp_view($this->lp_view_id); |
281
|
|
|
$oItem->set_prevent_reinit($this->prevent_reinit); |
282
|
|
|
// Don't use reference here as the next loop will make the pointed object change. |
283
|
|
|
$this->items[$itemId] = $oItem; |
284
|
|
|
$this->refs_list[$itemId] = $itemId; |
285
|
|
|
} |
286
|
|
|
break; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
// Setting the object level with variable $this->items[$i][parent] |
290
|
|
|
foreach ($this->items as $itemLPObject) { |
291
|
|
|
$level = self::get_level_for_item($this->items, $itemLPObject->db_id); |
292
|
|
|
$itemLPObject->level = $level; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// Setting the view in the item object. |
296
|
|
|
if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) { |
297
|
|
|
$this->items[$itemId]->set_lp_view($this->lp_view_id); |
298
|
|
|
if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) { |
299
|
|
|
$this->items[$itemId]->current_start_time = 0; |
300
|
|
|
$this->items[$itemId]->current_stop_time = 0; |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
if (!empty($lp_item_id_list)) { |
306
|
|
|
$lp_item_id_list_to_string = implode("','", $lp_item_id_list); |
307
|
|
|
if (!empty($lp_item_id_list_to_string)) { |
308
|
|
|
// Get last viewing vars. |
309
|
|
|
$itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW); |
310
|
|
|
// This query should only return one or zero result. |
311
|
|
|
$sql = "SELECT lp_item_id, status |
312
|
|
|
FROM $itemViewTable |
313
|
|
|
WHERE |
314
|
|
|
lp_view_id = ".$this->get_view_id()." AND |
315
|
|
|
lp_item_id IN ('".$lp_item_id_list_to_string."') |
316
|
|
|
ORDER BY view_count DESC "; |
317
|
|
|
$status_list = []; |
318
|
|
|
$res = Database::query($sql); |
319
|
|
|
while ($row = Database:: fetch_array($res)) { |
320
|
|
|
$status_list[$row['lp_item_id']] = $row['status']; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
foreach ($lp_item_id_list as $item_id) { |
324
|
|
|
if (isset($status_list[$item_id])) { |
325
|
|
|
$status = $status_list[$item_id]; |
326
|
|
|
|
327
|
|
|
if (is_object($this->items[$item_id])) { |
328
|
|
|
$this->items[$item_id]->set_status($status); |
329
|
|
|
if (empty($status)) { |
330
|
|
|
$this->items[$item_id]->set_status( |
331
|
|
|
$this->default_status |
332
|
|
|
); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
} else { |
336
|
|
|
if (!api_is_invitee()) { |
337
|
|
|
if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) { |
338
|
|
|
$this->items[$item_id]->set_status( |
339
|
|
|
$this->default_status |
340
|
|
|
); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
if (!empty($this->lp_view_id)) { |
344
|
|
|
// Add that row to the lp_item_view table so that |
345
|
|
|
// we have something to show in the stats page. |
346
|
|
|
$params = [ |
347
|
|
|
'lp_item_id' => $item_id, |
348
|
|
|
'lp_view_id' => $this->lp_view_id, |
349
|
|
|
'view_count' => 1, |
350
|
|
|
'status' => 'not attempted', |
351
|
|
|
'start_time' => time(), |
352
|
|
|
'total_time' => 0, |
353
|
|
|
'score' => 0, |
354
|
|
|
]; |
355
|
|
|
Database::insert($itemViewTable, $params); |
356
|
|
|
|
357
|
|
|
$this->items[$item_id]->set_lp_view( |
358
|
|
|
$this->lp_view_id |
359
|
|
|
); |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
$this->ordered_items = self::get_flat_ordered_items_list($entity, null); |
368
|
|
|
$this->max_ordered_items = 0; |
369
|
|
|
foreach ($this->ordered_items as $index => $dummy) { |
370
|
|
|
if ($index > $this->max_ordered_items && !empty($dummy)) { |
371
|
|
|
$this->max_ordered_items = $index; |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
// TODO: Define the current item better. |
375
|
|
|
$this->first(); |
376
|
|
|
if ($debug) { |
377
|
|
|
error_log('lp_view_session_id '.$this->lp_view_session_id); |
378
|
|
|
error_log('End of learnpath constructor for learnpath '.$this->get_id()); |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @return int |
385
|
|
|
*/ |
386
|
|
|
public function get_course_int_id() |
387
|
|
|
{ |
388
|
|
|
return $this->course_int_id ?? api_get_course_int_id(); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* @param $course_id |
393
|
|
|
* |
394
|
|
|
* @return int |
395
|
|
|
*/ |
396
|
|
|
public function set_course_int_id($course_id) |
397
|
|
|
{ |
398
|
|
|
return $this->course_int_id = (int) $course_id; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Function rewritten based on old_add_item() from Yannick Warnier. |
403
|
|
|
* Due the fact that users can decide where the item should come, I had to overlook this function and |
404
|
|
|
* I found it better to rewrite it. Old function is still available. |
405
|
|
|
* Added also the possibility to add a description. |
406
|
|
|
* |
407
|
|
|
* @param CLpItem $parent |
408
|
|
|
* @param int $previousId |
409
|
|
|
* @param string $type |
410
|
|
|
* @param int $id resource ID (ref) |
411
|
|
|
* @param string $title |
412
|
|
|
* @param string $description |
413
|
|
|
* @param int $prerequisites |
414
|
|
|
* @param int $maxTimeAllowed |
415
|
|
|
* @param int $userId |
416
|
|
|
* |
417
|
|
|
* @return int |
418
|
|
|
*/ |
419
|
|
|
public function add_item( |
420
|
|
|
?CLpItem $parent, |
421
|
|
|
$previousId, |
422
|
|
|
$type, |
423
|
|
|
$id, |
424
|
|
|
$title, |
425
|
|
|
$description = '', |
426
|
|
|
$prerequisites = 0, |
427
|
|
|
$maxTimeAllowed = 0 |
428
|
|
|
) { |
429
|
|
|
$type = empty($type) ? 'dir' : $type; |
430
|
|
|
$course_id = $this->course_info['real_id']; |
431
|
|
|
if (empty($course_id)) { |
432
|
|
|
// Sometimes Oogie doesn't catch the course info but sets $this->cc |
433
|
|
|
$this->course_info = api_get_course_info($this->cc); |
434
|
|
|
$course_id = $this->course_info['real_id']; |
435
|
|
|
} |
436
|
|
|
$id = (int) $id; |
437
|
|
|
$maxTimeAllowed = (int) $maxTimeAllowed; |
438
|
|
|
if (empty($maxTimeAllowed)) { |
439
|
|
|
$maxTimeAllowed = 0; |
440
|
|
|
} |
441
|
|
|
$maxScore = 100; |
442
|
|
|
if ('quiz' === $type && $id) { |
443
|
|
|
// Disabling the exercise if we add it inside a LP |
444
|
|
|
$exercise = new Exercise($course_id); |
445
|
|
|
$exercise->read($id); |
446
|
|
|
$maxScore = $exercise->getMaxScore(); |
447
|
|
|
|
448
|
|
|
$exercise->disable(); |
449
|
|
|
$exercise->save(); |
450
|
|
|
$title = $exercise->get_formated_title(); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
$lpItem = (new CLpItem()) |
454
|
|
|
->setTitle($title) |
455
|
|
|
->setDescription($description) |
456
|
|
|
->setPath($id) |
457
|
|
|
->setLp(api_get_lp_entity($this->get_id())) |
458
|
|
|
->setItemType($type) |
459
|
|
|
->setMaxScore($maxScore) |
460
|
|
|
->setMaxTimeAllowed($maxTimeAllowed) |
461
|
|
|
->setPrerequisite($prerequisites) |
462
|
|
|
//->setDisplayOrder($display_order + 1) |
463
|
|
|
//->setNextItemId((int) $next) |
464
|
|
|
//->setPreviousItemId($previous) |
465
|
|
|
; |
466
|
|
|
|
467
|
|
|
if (!empty($parent)) { |
468
|
|
|
$lpItem->setParent($parent); |
469
|
|
|
} |
470
|
|
|
$em = Database::getManager(); |
471
|
|
|
$em->persist($lpItem); |
472
|
|
|
$em->flush(); |
473
|
|
|
|
474
|
|
|
$new_item_id = $lpItem->getIid(); |
475
|
|
|
if ($new_item_id) { |
476
|
|
|
// @todo fix upload audio. |
477
|
|
|
// Upload audio. |
478
|
|
|
/*if (!empty($_FILES['mp3']['name'])) { |
479
|
|
|
// Create the audio folder if it does not exist yet. |
480
|
|
|
$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'; |
481
|
|
|
if (!is_dir($filepath.'audio')) { |
482
|
|
|
mkdir( |
483
|
|
|
$filepath.'audio', |
484
|
|
|
api_get_permissions_for_new_directories() |
485
|
|
|
); |
486
|
|
|
DocumentManager::addDocument( |
487
|
|
|
$_course, |
488
|
|
|
'/audio', |
489
|
|
|
'folder', |
490
|
|
|
0, |
491
|
|
|
'audio', |
492
|
|
|
'', |
493
|
|
|
0, |
494
|
|
|
true, |
495
|
|
|
null, |
496
|
|
|
$sessionId, |
497
|
|
|
$userId |
498
|
|
|
); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
$file_path = handle_uploaded_document( |
502
|
|
|
$_course, |
503
|
|
|
$_FILES['mp3'], |
504
|
|
|
api_get_path(SYS_COURSE_PATH).$_course['path'].'/document', |
505
|
|
|
'/audio', |
506
|
|
|
$userId, |
507
|
|
|
'', |
508
|
|
|
'', |
509
|
|
|
'', |
510
|
|
|
'', |
511
|
|
|
false |
512
|
|
|
); |
513
|
|
|
|
514
|
|
|
// Getting the filename only. |
515
|
|
|
$file_components = explode('/', $file_path); |
516
|
|
|
$file = $file_components[count($file_components) - 1]; |
517
|
|
|
|
518
|
|
|
// Store the mp3 file in the lp_item table. |
519
|
|
|
$sql = "UPDATE $tbl_lp_item SET |
520
|
|
|
audio = '".Database::escape_string($file)."' |
521
|
|
|
WHERE iid = '".intval($new_item_id)."'"; |
522
|
|
|
Database::query($sql); |
523
|
|
|
}*/ |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
return $new_item_id; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* Static admin function allowing addition of a learnpath to a course. |
531
|
|
|
* |
532
|
|
|
* @param string $courseCode |
533
|
|
|
* @param string $name |
534
|
|
|
* @param string $description |
535
|
|
|
* @param string $learnpath |
536
|
|
|
* @param string $origin |
537
|
|
|
* @param string $zipname Zip file containing the learnpath or directory containing the learnpath |
538
|
|
|
* @param string $published_on |
539
|
|
|
* @param string $expired_on |
540
|
|
|
* @param int $categoryId |
541
|
|
|
* @param int $userId |
542
|
|
|
* |
543
|
|
|
* @return CLp |
544
|
|
|
*/ |
545
|
|
|
public static function add_lp( |
546
|
|
|
$courseCode, |
547
|
|
|
$name, |
548
|
|
|
$description = '', |
549
|
|
|
$learnpath = 'guess', |
550
|
|
|
$origin = 'zip', |
551
|
|
|
$zipname = '', |
552
|
|
|
$published_on = '', |
553
|
|
|
$expired_on = '', |
554
|
|
|
$categoryId = 0, |
555
|
|
|
$userId = 0 |
556
|
|
|
) { |
557
|
|
|
global $charset; |
558
|
|
|
|
559
|
|
|
if (!empty($courseCode)) { |
560
|
|
|
$courseInfo = api_get_course_info($courseCode); |
561
|
|
|
$course_id = $courseInfo['real_id']; |
562
|
|
|
} else { |
563
|
|
|
$course_id = api_get_course_int_id(); |
564
|
|
|
$courseInfo = api_get_course_info(); |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
$categoryId = (int) $categoryId; |
568
|
|
|
|
569
|
|
|
if (empty($published_on)) { |
570
|
|
|
$published_on = null; |
571
|
|
|
} else { |
572
|
|
|
$published_on = api_get_utc_datetime($published_on, true, true); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
if (empty($expired_on)) { |
576
|
|
|
$expired_on = null; |
577
|
|
|
} else { |
578
|
|
|
$expired_on = api_get_utc_datetime($expired_on, true, true); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
$description = Database::escape_string(api_htmlentities($description, ENT_QUOTES)); |
582
|
|
|
$type = 1; |
583
|
|
|
switch ($learnpath) { |
584
|
|
|
case 'guess': |
585
|
|
|
case 'aicc': |
586
|
|
|
break; |
587
|
|
|
case 'dokeos': |
588
|
|
|
case 'chamilo': |
589
|
|
|
$type = 1; |
590
|
|
|
break; |
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
$sessionEntity = api_get_session_entity(); |
594
|
|
|
$courseEntity = api_get_course_entity($courseInfo['real_id']); |
595
|
|
|
$lp = null; |
596
|
|
|
switch ($origin) { |
597
|
|
|
case 'zip': |
598
|
|
|
// Check zip name string. If empty, we are currently creating a new Chamilo learnpath. |
599
|
|
|
break; |
600
|
|
|
case 'manual': |
601
|
|
|
default: |
602
|
|
|
/*$get_max = "SELECT MAX(display_order) |
603
|
|
|
FROM $tbl_lp WHERE c_id = $course_id"; |
604
|
|
|
$res_max = Database::query($get_max); |
605
|
|
|
if (Database::num_rows($res_max) < 1) { |
606
|
|
|
$dsp = 1; |
607
|
|
|
} else { |
608
|
|
|
$row = Database::fetch_array($res_max); |
609
|
|
|
$dsp = $row[0] + 1; |
610
|
|
|
}*/ |
611
|
|
|
|
612
|
|
|
$category = null; |
613
|
|
|
if (!empty($categoryId)) { |
614
|
|
|
$category = Container::getLpCategoryRepository()->find($categoryId); |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
$lpRepo = Container::getLpRepository(); |
618
|
|
|
|
619
|
|
|
$lp = (new CLp()) |
620
|
|
|
->setLpType($type) |
621
|
|
|
->setTitle($name) |
622
|
|
|
->setDescription($description) |
623
|
|
|
->setCategory($category) |
624
|
|
|
->setPublishedOn($published_on) |
625
|
|
|
->setExpiredOn($expired_on) |
626
|
|
|
->setParent($courseEntity) |
627
|
|
|
->addCourseLink($courseEntity, $sessionEntity) |
628
|
|
|
; |
629
|
|
|
$lpRepo->createLp($lp); |
630
|
|
|
|
631
|
|
|
break; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
return $lp; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* Auto completes the parents of an item in case it's been completed or passed. |
639
|
|
|
* |
640
|
|
|
* @param int $item Optional ID of the item from which to look for parents |
641
|
|
|
*/ |
642
|
|
|
public function autocomplete_parents($item) |
643
|
|
|
{ |
644
|
|
|
$debug = $this->debug; |
645
|
|
|
|
646
|
|
|
if (empty($item)) { |
647
|
|
|
$item = $this->current; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
$currentItem = $this->getItem($item); |
651
|
|
|
if ($currentItem) { |
652
|
|
|
$parent_id = $currentItem->get_parent(); |
653
|
|
|
$parent = $this->getItem($parent_id); |
654
|
|
|
if ($parent) { |
655
|
|
|
// if $item points to an object and there is a parent. |
656
|
|
|
if ($debug) { |
657
|
|
|
error_log( |
658
|
|
|
'Autocompleting parent of item '.$item.' '. |
659
|
|
|
$currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ', |
660
|
|
|
0 |
661
|
|
|
); |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
// New experiment including failed and browsed in completed status. |
665
|
|
|
//$current_status = $currentItem->get_status(); |
666
|
|
|
//if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') { |
667
|
|
|
// Fixes chapter auto complete |
668
|
|
|
if (true) { |
669
|
|
|
// If the current item is completed or passes or succeeded. |
670
|
|
|
$updateParentStatus = true; |
671
|
|
|
if ($debug) { |
672
|
|
|
error_log('Status of current item is alright'); |
673
|
|
|
} |
674
|
|
|
|
675
|
|
|
foreach ($parent->get_children() as $childItemId) { |
676
|
|
|
$childItem = $this->getItem($childItemId); |
677
|
|
|
|
678
|
|
|
// If children was not set try to get the info |
679
|
|
|
if (empty($childItem->db_item_view_id)) { |
680
|
|
|
$childItem->set_lp_view($this->lp_view_id); |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
// Check all his brothers (parent's children) for completion status. |
684
|
|
|
if ($childItemId != $item) { |
685
|
|
|
if ($debug) { |
686
|
|
|
error_log( |
687
|
|
|
'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(), |
688
|
|
|
0 |
689
|
|
|
); |
690
|
|
|
} |
691
|
|
|
// Trying completing parents of failed and browsed items as well. |
692
|
|
|
if ($childItem->status_is( |
693
|
|
|
[ |
694
|
|
|
'completed', |
695
|
|
|
'passed', |
696
|
|
|
'succeeded', |
697
|
|
|
'browsed', |
698
|
|
|
'failed', |
699
|
|
|
] |
700
|
|
|
) |
701
|
|
|
) { |
702
|
|
|
// Keep completion status to true. |
703
|
|
|
continue; |
704
|
|
|
} else { |
705
|
|
|
if ($debug > 2) { |
706
|
|
|
error_log( |
707
|
|
|
'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, |
708
|
|
|
0 |
709
|
|
|
); |
710
|
|
|
} |
711
|
|
|
$updateParentStatus = false; |
712
|
|
|
break; |
713
|
|
|
} |
714
|
|
|
} |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
if ($updateParentStatus) { |
718
|
|
|
// If all the children were completed: |
719
|
|
|
$parent->set_status('completed'); |
720
|
|
|
$parent->save(false, $this->prerequisites_match($parent->get_id())); |
721
|
|
|
// Force the status to "completed" |
722
|
|
|
//$this->update_queue[$parent->get_id()] = $parent->get_status(); |
723
|
|
|
$this->update_queue[$parent->get_id()] = 'completed'; |
724
|
|
|
if ($debug) { |
725
|
|
|
error_log( |
726
|
|
|
'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '. |
727
|
|
|
print_r($this->update_queue, 1), |
728
|
|
|
0 |
729
|
|
|
); |
730
|
|
|
} |
731
|
|
|
// Recursive call. |
732
|
|
|
$this->autocomplete_parents($parent->get_id()); |
733
|
|
|
} |
734
|
|
|
} |
735
|
|
|
} else { |
736
|
|
|
if ($debug) { |
737
|
|
|
error_log("Parent #$parent_id does not exists"); |
738
|
|
|
} |
739
|
|
|
} |
740
|
|
|
} else { |
741
|
|
|
if ($debug) { |
742
|
|
|
error_log("#$item is an item that doesn't have parents"); |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
/** |
748
|
|
|
* Closes the current resource. |
749
|
|
|
* |
750
|
|
|
* Stops the timer |
751
|
|
|
* Saves into the database if required |
752
|
|
|
* Clears the current resource data from this object |
753
|
|
|
* |
754
|
|
|
* @return bool True on success, false on failure |
755
|
|
|
*/ |
756
|
|
|
public function close() |
757
|
|
|
{ |
758
|
|
|
if (empty($this->lp_id)) { |
759
|
|
|
$this->error = 'Trying to close this learnpath but no ID is set'; |
760
|
|
|
|
761
|
|
|
return false; |
762
|
|
|
} |
763
|
|
|
$this->current_time_stop = time(); |
764
|
|
|
$this->ordered_items = []; |
765
|
|
|
$this->index = 0; |
766
|
|
|
unset($this->lp_id); |
767
|
|
|
//unset other stuff |
768
|
|
|
return true; |
769
|
|
|
} |
770
|
|
|
|
771
|
|
|
/** |
772
|
|
|
* Static admin function allowing removal of a learnpath. |
773
|
|
|
* |
774
|
|
|
* @param array $courseInfo |
775
|
|
|
* @param int $id Learnpath ID |
776
|
|
|
* @param string $delete Whether to delete data or keep it (default: 'keep', others: 'remove') |
777
|
|
|
* |
778
|
|
|
* @return bool True on success, false on failure (might change that to return number of elements deleted) |
779
|
|
|
*/ |
780
|
|
|
public function delete($courseInfo = null, $id = null, $delete = 'keep') |
781
|
|
|
{ |
782
|
|
|
$course_id = api_get_course_int_id(); |
783
|
|
|
if (!empty($courseInfo)) { |
784
|
|
|
$course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id; |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
// TODO: Implement a way of getting this to work when the current object is not set. |
788
|
|
|
// In clear: implement this in the item class as well (abstract class) and use the given ID in queries. |
789
|
|
|
// If an ID is specifically given and the current LP is not the same, prevent delete. |
790
|
|
|
if (!empty($id) && ($id != $this->lp_id)) { |
791
|
|
|
return false; |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
$course = api_get_course_entity(); |
795
|
|
|
$session = api_get_session_entity(); |
796
|
|
|
|
797
|
|
|
//$lp_item = Database::get_course_table(TABLE_LP_ITEM); |
798
|
|
|
//$lp_view = Database::get_course_table(TABLE_LP_VIEW); |
799
|
|
|
//$lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); |
800
|
|
|
|
801
|
|
|
// Delete lp item id. |
802
|
|
|
//foreach ($this->items as $lpItemId => $dummy) { |
803
|
|
|
// $sql = "DELETE FROM $lp_item_view |
804
|
|
|
// WHERE lp_item_id = '".$lpItemId."'"; |
805
|
|
|
// Database::query($sql); |
806
|
|
|
//} |
807
|
|
|
|
808
|
|
|
// Proposed by Christophe (nickname: clefevre) |
809
|
|
|
//$sql = "DELETE FROM $lp_item |
810
|
|
|
// WHERE lp_id = ".$this->lp_id; |
811
|
|
|
//Database::query($sql); |
812
|
|
|
|
813
|
|
|
//$sql = "DELETE FROM $lp_view |
814
|
|
|
// WHERE lp_id = ".$this->lp_id; |
815
|
|
|
//Database::query($sql); |
816
|
|
|
|
817
|
|
|
//$table = Database::get_course_table(TABLE_LP_REL_USERGROUP); |
818
|
|
|
//$sql = "DELETE FROM $table |
819
|
|
|
// WHERE |
820
|
|
|
// lp_id = {$this->lp_id}"; |
821
|
|
|
//Database::query($sql); |
822
|
|
|
|
823
|
|
|
$lp = Container::getLpRepository()->find($this->lp_id); |
824
|
|
|
|
825
|
|
|
Database::getManager() |
826
|
|
|
->getRepository(ResourceLink::class) |
827
|
|
|
->removeByResourceInContext($lp, $course, $session); |
828
|
|
|
|
829
|
|
|
$link_info = GradebookUtils::isResourceInCourseGradebook( |
830
|
|
|
api_get_course_int_id(), |
831
|
|
|
4, |
832
|
|
|
$id, |
833
|
|
|
api_get_session_id() |
834
|
|
|
); |
835
|
|
|
|
836
|
|
|
if (!empty($link_info)) { |
837
|
|
|
GradebookUtils::remove_resource_from_course_gradebook($link_info['id']); |
838
|
|
|
} |
839
|
|
|
|
840
|
|
|
//if ('true' === api_get_setting('search_enabled')) { |
841
|
|
|
// delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id); |
842
|
|
|
//} |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* Removes all the children of one item - dangerous! |
847
|
|
|
* |
848
|
|
|
* @param int $id Element ID of which children have to be removed |
849
|
|
|
* |
850
|
|
|
* @return int Total number of children removed |
851
|
|
|
*/ |
852
|
|
|
public function delete_children_items($id) |
853
|
|
|
{ |
854
|
|
|
$course_id = $this->course_info['real_id']; |
855
|
|
|
|
856
|
|
|
$num = 0; |
857
|
|
|
$id = (int) $id; |
858
|
|
|
if (empty($id) || empty($course_id)) { |
859
|
|
|
return false; |
860
|
|
|
} |
861
|
|
|
$lp_item = Database::get_course_table(TABLE_LP_ITEM); |
862
|
|
|
$sql = "SELECT * FROM $lp_item |
863
|
|
|
WHERE parent_item_id = $id"; |
864
|
|
|
$res = Database::query($sql); |
865
|
|
|
while ($row = Database::fetch_array($res)) { |
866
|
|
|
$num += $this->delete_children_items($row['iid']); |
867
|
|
|
$sql = "DELETE FROM $lp_item |
868
|
|
|
WHERE iid = ".$row['iid']; |
869
|
|
|
Database::query($sql); |
870
|
|
|
$num++; |
871
|
|
|
} |
872
|
|
|
|
873
|
|
|
return $num; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* Removes an item from the current learnpath. |
878
|
|
|
* |
879
|
|
|
* @param int $id Elem ID (0 if first) |
880
|
|
|
* |
881
|
|
|
* @return int Number of elements moved |
882
|
|
|
* |
883
|
|
|
* @todo implement resource removal |
884
|
|
|
*/ |
885
|
|
|
public function delete_item($id) |
886
|
|
|
{ |
887
|
|
|
$course_id = api_get_course_int_id(); |
888
|
|
|
$id = (int) $id; |
889
|
|
|
// TODO: Implement the resource removal. |
890
|
|
|
if (empty($id) || empty($course_id)) { |
891
|
|
|
return false; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
$repo = Container::getLpItemRepository(); |
895
|
|
|
$item = $repo->find($id); |
896
|
|
|
if (null === $item) { |
897
|
|
|
return false; |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
$em = Database::getManager(); |
901
|
|
|
$repo->removeFromTree($item); |
902
|
|
|
$em->flush(); |
903
|
|
|
$lp_item = Database::get_course_table(TABLE_LP_ITEM); |
904
|
|
|
|
905
|
|
|
//Removing prerequisites since the item will not longer exist |
906
|
|
|
$sql_all = "UPDATE $lp_item SET prerequisite = '' |
907
|
|
|
WHERE prerequisite = '$id'"; |
908
|
|
|
Database::query($sql_all); |
909
|
|
|
|
910
|
|
|
$sql = "UPDATE $lp_item |
911
|
|
|
SET previous_item_id = ".$this->getLastInFirstLevel()." |
912
|
|
|
WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'"; |
913
|
|
|
Database::query($sql); |
914
|
|
|
|
915
|
|
|
// Remove from search engine if enabled. |
916
|
|
|
if ('true' === api_get_setting('search_enabled')) { |
917
|
|
|
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); |
918
|
|
|
$sql = 'SELECT * FROM %s |
919
|
|
|
WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d |
920
|
|
|
LIMIT 1'; |
921
|
|
|
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); |
922
|
|
|
$res = Database::query($sql); |
923
|
|
|
if (Database::num_rows($res) > 0) { |
924
|
|
|
$row2 = Database::fetch_array($res); |
925
|
|
|
$di = new ChamiloIndexer(); |
926
|
|
|
$di->remove_document($row2['search_did']); |
927
|
|
|
} |
928
|
|
|
$sql = 'DELETE FROM %s |
929
|
|
|
WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d |
930
|
|
|
LIMIT 1'; |
931
|
|
|
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); |
932
|
|
|
Database::query($sql); |
933
|
|
|
} |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* Updates an item's content in place. |
938
|
|
|
* |
939
|
|
|
* @param int $id Element ID |
940
|
|
|
* @param int $parent Parent item ID |
941
|
|
|
* @param int $previous Previous item ID |
942
|
|
|
* @param string $title Item title |
943
|
|
|
* @param string $description Item description |
944
|
|
|
* @param string $prerequisites Prerequisites (optional) |
945
|
|
|
* @param array $audio The array resulting of the $_FILES[mp3] element |
946
|
|
|
* @param int $max_time_allowed |
947
|
|
|
* @param string $url |
948
|
|
|
* |
949
|
|
|
* @return bool True on success, false on error |
950
|
|
|
*/ |
951
|
|
|
public function edit_item( |
952
|
|
|
$id, |
953
|
|
|
$parent, |
954
|
|
|
$previous, |
955
|
|
|
$title, |
956
|
|
|
$description, |
957
|
|
|
$prerequisites = '0', |
958
|
|
|
$audio = [], |
959
|
|
|
$max_time_allowed = 0, |
960
|
|
|
$url = '' |
961
|
|
|
) { |
962
|
|
|
$_course = api_get_course_info(); |
963
|
|
|
$id = (int) $id; |
964
|
|
|
|
965
|
|
|
if (empty($id) || empty($_course)) { |
966
|
|
|
return false; |
967
|
|
|
} |
968
|
|
|
$repo = Container::getLpItemRepository(); |
969
|
|
|
/** @var CLpItem $item */ |
970
|
|
|
$item = $repo->find($id); |
971
|
|
|
if (null === $item) { |
972
|
|
|
return false; |
973
|
|
|
} |
974
|
|
|
|
975
|
|
|
$item |
976
|
|
|
->setTitle($title) |
977
|
|
|
->setDescription($description) |
978
|
|
|
->setPrerequisite($prerequisites) |
979
|
|
|
->setMaxTimeAllowed((int) $max_time_allowed) |
980
|
|
|
; |
981
|
|
|
|
982
|
|
|
$em = Database::getManager(); |
983
|
|
|
if (!empty($parent)) { |
984
|
|
|
$parent = $repo->find($parent); |
985
|
|
|
$item->setParent($parent); |
986
|
|
|
} else { |
987
|
|
|
$item->setParent(null); |
988
|
|
|
} |
989
|
|
|
|
990
|
|
|
if (!empty($previous)) { |
991
|
|
|
$previous = $repo->find($previous); |
992
|
|
|
$repo->persistAsNextSiblingOf( $item, $previous); |
993
|
|
|
} else { |
994
|
|
|
$em->persist($item); |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
$em->flush(); |
998
|
|
|
|
999
|
|
|
if ('link' === $item->getItemType()) { |
1000
|
|
|
$link = new Link(); |
1001
|
|
|
$linkId = $item->getPath(); |
1002
|
|
|
$link->updateLink($linkId, $url); |
1003
|
|
|
} |
1004
|
|
|
} |
1005
|
|
|
|
1006
|
|
|
/** |
1007
|
|
|
* Updates an item's prereq in place. |
1008
|
|
|
* |
1009
|
|
|
* @param int $id Element ID |
1010
|
|
|
* @param string $prerequisite_id Prerequisite Element ID |
1011
|
|
|
* @param int $minScore Prerequisite min score |
1012
|
|
|
* @param int $maxScore Prerequisite max score |
1013
|
|
|
* |
1014
|
|
|
* @return bool True on success, false on error |
1015
|
|
|
*/ |
1016
|
|
|
public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100) |
1017
|
|
|
{ |
1018
|
|
|
$id = (int) $id; |
1019
|
|
|
|
1020
|
|
|
if (empty($id)) { |
1021
|
|
|
return false; |
1022
|
|
|
} |
1023
|
|
|
$prerequisite_id = (int) $prerequisite_id; |
1024
|
|
|
|
1025
|
|
|
if (empty($minScore) || $minScore < 0) { |
1026
|
|
|
$minScore = 0; |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
|
|
if (empty($maxScore) || $maxScore < 0) { |
1030
|
|
|
$maxScore = 100; |
1031
|
|
|
} |
1032
|
|
|
|
1033
|
|
|
$minScore = (float) $minScore; |
1034
|
|
|
$maxScore = (float) $maxScore; |
1035
|
|
|
|
1036
|
|
|
if (empty($prerequisite_id)) { |
1037
|
|
|
$prerequisite_id = 'NULL'; |
1038
|
|
|
$minScore = 0; |
1039
|
|
|
$maxScore = 100; |
1040
|
|
|
} |
1041
|
|
|
|
1042
|
|
|
$table = Database::get_course_table(TABLE_LP_ITEM); |
1043
|
|
|
$sql = " UPDATE $table |
1044
|
|
|
SET |
1045
|
|
|
prerequisite = $prerequisite_id , |
1046
|
|
|
prerequisite_min_score = $minScore , |
1047
|
|
|
prerequisite_max_score = $maxScore |
1048
|
|
|
WHERE iid = $id"; |
1049
|
|
|
Database::query($sql); |
1050
|
|
|
|
1051
|
|
|
return true; |
1052
|
|
|
} |
1053
|
|
|
|
1054
|
|
|
/** |
1055
|
|
|
* Get the specific prefix index terms of this learning path. |
1056
|
|
|
* |
1057
|
|
|
* @param string $prefix |
1058
|
|
|
* |
1059
|
|
|
* @return array Array of terms |
1060
|
|
|
*/ |
1061
|
|
|
public function get_common_index_terms_by_prefix($prefix) |
1062
|
|
|
{ |
1063
|
|
|
$terms = get_specific_field_values_list_by_prefix( |
1064
|
|
|
$prefix, |
1065
|
|
|
$this->cc, |
1066
|
|
|
TOOL_LEARNPATH, |
1067
|
|
|
$this->lp_id |
1068
|
|
|
); |
1069
|
|
|
$prefix_terms = []; |
1070
|
|
|
if (!empty($terms)) { |
1071
|
|
|
foreach ($terms as $term) { |
1072
|
|
|
$prefix_terms[] = $term['value']; |
1073
|
|
|
} |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
return $prefix_terms; |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
|
|
/** |
1080
|
|
|
* Gets the number of items currently completed. |
1081
|
|
|
* |
1082
|
|
|
* @param bool Flag to determine the failed status is not considered progressed |
1083
|
|
|
* |
1084
|
|
|
* @return int The number of items currently completed |
1085
|
|
|
*/ |
1086
|
|
|
public function get_complete_items_count(bool $failedStatusException = false): int |
1087
|
|
|
{ |
1088
|
|
|
$i = 0; |
1089
|
|
|
$completedStatusList = [ |
1090
|
|
|
'completed', |
1091
|
|
|
'passed', |
1092
|
|
|
'succeeded', |
1093
|
|
|
'browsed', |
1094
|
|
|
]; |
1095
|
|
|
|
1096
|
|
|
if (!$failedStatusException) { |
1097
|
|
|
$completedStatusList[] = 'failed'; |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
foreach ($this->items as $id => $dummy) { |
1101
|
|
|
// Trying failed and browsed considered "progressed" as well. |
1102
|
|
|
if ($this->items[$id]->status_is($completedStatusList) && |
1103
|
|
|
'dir' !== $this->items[$id]->get_type() |
1104
|
|
|
) { |
1105
|
|
|
$i++; |
1106
|
|
|
} |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
return $i; |
1110
|
|
|
} |
1111
|
|
|
|
1112
|
|
|
/** |
1113
|
|
|
* Gets the current item ID. |
1114
|
|
|
* |
1115
|
|
|
* @return int The current learnpath item id |
1116
|
|
|
*/ |
1117
|
|
|
public function get_current_item_id() |
1118
|
|
|
{ |
1119
|
|
|
$current = 0; |
1120
|
|
|
if (!empty($this->current)) { |
1121
|
|
|
$current = (int) $this->current; |
1122
|
|
|
} |
1123
|
|
|
|
1124
|
|
|
return $current; |
1125
|
|
|
} |
1126
|
|
|
|
1127
|
|
|
/** |
1128
|
|
|
* Force to get the first learnpath item id. |
1129
|
|
|
* |
1130
|
|
|
* @return int The current learnpath item id |
1131
|
|
|
*/ |
1132
|
|
|
public function get_first_item_id() |
1133
|
|
|
{ |
1134
|
|
|
$current = 0; |
1135
|
|
|
if (is_array($this->ordered_items)) { |
1136
|
|
|
$current = $this->ordered_items[0]; |
1137
|
|
|
} |
1138
|
|
|
|
1139
|
|
|
return $current; |
1140
|
|
|
} |
1141
|
|
|
|
1142
|
|
|
/** |
1143
|
|
|
* Gets the total number of items available for viewing in this SCORM. |
1144
|
|
|
* |
1145
|
|
|
* @return int The total number of items |
1146
|
|
|
*/ |
1147
|
|
|
public function get_total_items_count() |
1148
|
|
|
{ |
1149
|
|
|
return count($this->items); |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
/** |
1153
|
|
|
* Gets the total number of items available for viewing in this SCORM but without chapters. |
1154
|
|
|
* |
1155
|
|
|
* @return int The total no-chapters number of items |
1156
|
|
|
*/ |
1157
|
|
|
public function getTotalItemsCountWithoutDirs() |
1158
|
|
|
{ |
1159
|
|
|
$total = 0; |
1160
|
|
|
$typeListNotToCount = self::getChapterTypes(); |
1161
|
|
|
foreach ($this->items as $temp2) { |
1162
|
|
|
if (!in_array($temp2->get_type(), $typeListNotToCount)) { |
1163
|
|
|
$total++; |
1164
|
|
|
} |
1165
|
|
|
} |
1166
|
|
|
|
1167
|
|
|
return $total; |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
/** |
1171
|
|
|
* Sets the first element URL. |
1172
|
|
|
*/ |
1173
|
|
|
public function first() |
1174
|
|
|
{ |
1175
|
|
|
if ($this->debug > 0) { |
1176
|
|
|
error_log('In learnpath::first()', 0); |
1177
|
|
|
error_log('$this->last_item_seen '.$this->last_item_seen); |
1178
|
|
|
} |
1179
|
|
|
|
1180
|
|
|
// Test if the last_item_seen exists and is not a dir. |
1181
|
|
|
if (0 == count($this->ordered_items)) { |
1182
|
|
|
$this->index = 0; |
1183
|
|
|
} |
1184
|
|
|
|
1185
|
|
|
if (!empty($this->last_item_seen) && |
1186
|
|
|
!empty($this->items[$this->last_item_seen]) && |
1187
|
|
|
'dir' !== $this->items[$this->last_item_seen]->get_type() |
1188
|
|
|
//with this change (below) the LP will NOT go to the next item, it will take lp item we left |
1189
|
|
|
//&& !$this->items[$this->last_item_seen]->is_done() |
1190
|
|
|
) { |
1191
|
|
|
if ($this->debug > 2) { |
1192
|
|
|
error_log( |
1193
|
|
|
'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '. |
1194
|
|
|
$this->items[$this->last_item_seen]->get_type() |
1195
|
|
|
); |
1196
|
|
|
} |
1197
|
|
|
$index = -1; |
1198
|
|
|
foreach ($this->ordered_items as $myindex => $item_id) { |
1199
|
|
|
if ($item_id == $this->last_item_seen) { |
1200
|
|
|
$index = $myindex; |
1201
|
|
|
break; |
1202
|
|
|
} |
1203
|
|
|
} |
1204
|
|
|
if (-1 == $index) { |
1205
|
|
|
// Index hasn't changed, so item not found - panic (this shouldn't happen). |
1206
|
|
|
if ($this->debug > 2) { |
1207
|
|
|
error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0); |
1208
|
|
|
} |
1209
|
|
|
|
1210
|
|
|
return false; |
1211
|
|
|
} else { |
1212
|
|
|
$this->last = $this->last_item_seen; |
1213
|
|
|
$this->current = $this->last_item_seen; |
1214
|
|
|
$this->index = $index; |
1215
|
|
|
} |
1216
|
|
|
} else { |
1217
|
|
|
if ($this->debug > 2) { |
1218
|
|
|
error_log('In learnpath::first() - No last item seen', 0); |
1219
|
|
|
} |
1220
|
|
|
$index = 0; |
1221
|
|
|
// Loop through all ordered items and stop at the first item that is |
1222
|
|
|
// not a directory *and* that has not been completed yet. |
1223
|
|
|
while (!empty($this->ordered_items[$index]) && |
1224
|
|
|
is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') && |
1225
|
|
|
( |
1226
|
|
|
'dir' === $this->items[$this->ordered_items[$index]]->get_type() || |
1227
|
|
|
true === $this->items[$this->ordered_items[$index]]->is_done() |
1228
|
|
|
) && $index < $this->max_ordered_items |
1229
|
|
|
) { |
1230
|
|
|
$index++; |
1231
|
|
|
} |
1232
|
|
|
|
1233
|
|
|
$this->last = $this->current; |
1234
|
|
|
// current is |
1235
|
|
|
$this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null; |
1236
|
|
|
$this->index = $index; |
1237
|
|
|
if ($this->debug > 2) { |
1238
|
|
|
error_log('$index '.$index); |
1239
|
|
|
error_log('In learnpath::first() - No last item seen'); |
1240
|
|
|
error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')'); |
1241
|
|
|
} |
1242
|
|
|
} |
1243
|
|
|
if ($this->debug > 2) { |
1244
|
|
|
error_log('In learnpath::first() - First item is '.$this->get_current_item_id()); |
1245
|
|
|
} |
1246
|
|
|
} |
1247
|
|
|
|
1248
|
|
|
/** |
1249
|
|
|
* Gets the js library from the database. |
1250
|
|
|
* |
1251
|
|
|
* @return string The name of the javascript library to be used |
1252
|
|
|
*/ |
1253
|
|
|
public function get_js_lib() |
1254
|
|
|
{ |
1255
|
|
|
$lib = ''; |
1256
|
|
|
if (!empty($this->js_lib)) { |
1257
|
|
|
$lib = $this->js_lib; |
1258
|
|
|
} |
1259
|
|
|
|
1260
|
|
|
return $lib; |
1261
|
|
|
} |
1262
|
|
|
|
1263
|
|
|
/** |
1264
|
|
|
* Gets the learnpath database ID. |
1265
|
|
|
* |
1266
|
|
|
* @return int Learnpath ID in the lp table |
1267
|
|
|
*/ |
1268
|
|
|
public function get_id() |
1269
|
|
|
{ |
1270
|
|
|
if (!empty($this->lp_id)) { |
1271
|
|
|
return (int) $this->lp_id; |
1272
|
|
|
} |
1273
|
|
|
|
1274
|
|
|
return 0; |
1275
|
|
|
} |
1276
|
|
|
|
1277
|
|
|
/** |
1278
|
|
|
* Gets the last element URL. |
1279
|
|
|
* |
1280
|
|
|
* @return string URL to load into the viewer |
1281
|
|
|
*/ |
1282
|
|
|
public function get_last() |
1283
|
|
|
{ |
1284
|
|
|
// This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices" |
1285
|
|
|
if (count($this->ordered_items) > 0) { |
1286
|
|
|
$this->index = count($this->ordered_items) - 1; |
1287
|
|
|
|
1288
|
|
|
return $this->ordered_items[$this->index]; |
1289
|
|
|
} |
1290
|
|
|
|
1291
|
|
|
return false; |
1292
|
|
|
} |
1293
|
|
|
|
1294
|
|
|
/** |
1295
|
|
|
* Get the last element in the first level. |
1296
|
|
|
* Unlike learnpath::get_last this function doesn't consider the subsection' elements. |
1297
|
|
|
* |
1298
|
|
|
* @return mixed |
1299
|
|
|
*/ |
1300
|
|
|
public function getLastInFirstLevel() |
1301
|
|
|
{ |
1302
|
|
|
try { |
1303
|
|
|
$lastId = Database::getManager() |
1304
|
|
|
->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i |
1305
|
|
|
WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC') |
1306
|
|
|
->setMaxResults(1) |
1307
|
|
|
->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM]) |
1308
|
|
|
->getSingleScalarResult(); |
1309
|
|
|
|
1310
|
|
|
return $lastId; |
1311
|
|
|
} catch (Exception $exception) { |
1312
|
|
|
return 0; |
1313
|
|
|
} |
1314
|
|
|
} |
1315
|
|
|
|
1316
|
|
|
/** |
1317
|
|
|
* Gets the navigation bar for the learnpath display screen. |
1318
|
|
|
* |
1319
|
|
|
* @param string $barId |
1320
|
|
|
* |
1321
|
|
|
* @return string The HTML string to use as a navigation bar |
1322
|
|
|
*/ |
1323
|
|
|
public function get_navigation_bar($barId = '') |
1324
|
|
|
{ |
1325
|
|
|
if (empty($barId)) { |
1326
|
|
|
$barId = 'control-top'; |
1327
|
|
|
} |
1328
|
|
|
$lpId = $this->lp_id; |
1329
|
|
|
$mycurrentitemid = $this->get_current_item_id(); |
1330
|
|
|
$reportingText = get_lang('Reporting'); |
1331
|
|
|
$previousText = get_lang('Previous'); |
1332
|
|
|
$nextText = get_lang('Next'); |
1333
|
|
|
$fullScreenText = get_lang('Back to normal screen'); |
1334
|
|
|
|
1335
|
|
|
$settings = api_get_setting('lp.lp_view_settings', true); |
1336
|
|
|
$display = $settings['display'] ?? false; |
1337
|
|
|
$icon = Display::getMdiIcon('information'); |
1338
|
|
|
|
1339
|
|
|
$reportingIcon = ' |
1340
|
|
|
<a class="icon-toolbar" |
1341
|
|
|
id="stats_link" |
1342
|
|
|
href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" |
1343
|
|
|
onclick="window.parent.API.save_asset(); return true;" |
1344
|
|
|
target="content_name" title="'.$reportingText.'"> |
1345
|
|
|
'.$icon.'<span class="sr-only">'.$reportingText.'</span> |
1346
|
|
|
</a>'; |
1347
|
|
|
|
1348
|
|
|
if (!empty($display)) { |
1349
|
|
|
$showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true; |
1350
|
|
|
if (false === $showReporting) { |
1351
|
|
|
$reportingIcon = ''; |
1352
|
|
|
} |
1353
|
|
|
} |
1354
|
|
|
|
1355
|
|
|
$hideArrows = false; |
1356
|
|
|
if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) { |
1357
|
|
|
$hideArrows = $settings['display']['hide_lp_arrow_navigation']; |
1358
|
|
|
} |
1359
|
|
|
|
1360
|
|
|
$previousIcon = ''; |
1361
|
|
|
$nextIcon = ''; |
1362
|
|
|
if (false === $hideArrows) { |
1363
|
|
|
$icon = Display::getMdiIcon('chevron-left'); |
1364
|
|
|
$previousIcon = ' |
1365
|
|
|
<button class="icon-toolbar" id="scorm-previous" type="button" |
1366
|
|
|
onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'"> |
1367
|
|
|
'.$icon.'<span class="sr-only">'.$previousText.'</span> |
1368
|
|
|
</button>'; |
1369
|
|
|
|
1370
|
|
|
$icon = Display::getMdiIcon('chevron-right'); |
1371
|
|
|
$nextIcon = ' |
1372
|
|
|
<button class="icon-toolbar" id="scorm-next" type="button" |
1373
|
|
|
onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'"> |
1374
|
|
|
'.$icon.'<span class="sr-only">'.$nextText.'</span> |
1375
|
|
|
</button>'; |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
if ('fullscreen' === $this->mode) { |
1379
|
|
|
$icon = Display::getMdiIcon('view-column'); |
1380
|
|
|
$navbar = ' |
1381
|
|
|
<span id="'.$barId.'" class="buttons"> |
1382
|
|
|
'.$reportingIcon.' |
1383
|
|
|
'.$previousIcon.' |
1384
|
|
|
'.$nextIcon.' |
1385
|
|
|
<a class="icon-toolbar" id="view-embedded" |
1386
|
|
|
href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'"> |
1387
|
|
|
'.$icon.'<span class="sr-only">'.$fullScreenText.'</span> |
1388
|
|
|
</a> |
1389
|
|
|
</span>'; |
1390
|
|
|
} else { |
1391
|
|
|
$navbar = ' |
1392
|
|
|
<span id="'.$barId.'" class="buttons text-right"> |
1393
|
|
|
'.$reportingIcon.' |
1394
|
|
|
'.$previousIcon.' |
1395
|
|
|
'.$nextIcon.' |
1396
|
|
|
</span>'; |
1397
|
|
|
} |
1398
|
|
|
|
1399
|
|
|
return $navbar; |
1400
|
|
|
} |
1401
|
|
|
|
1402
|
|
|
/** |
1403
|
|
|
* Gets the next resource in queue (url). |
1404
|
|
|
* |
1405
|
|
|
* @return string URL to load into the viewer |
1406
|
|
|
*/ |
1407
|
|
|
public function get_next_index() |
1408
|
|
|
{ |
1409
|
|
|
// TODO |
1410
|
|
|
$index = $this->index; |
1411
|
|
|
$index++; |
1412
|
|
|
while ( |
1413
|
|
|
!empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) && |
1414
|
|
|
$index < $this->max_ordered_items |
1415
|
|
|
) { |
1416
|
|
|
$index++; |
1417
|
|
|
if ($index == $this->max_ordered_items) { |
1418
|
|
|
if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) { |
1419
|
|
|
return $this->index; |
1420
|
|
|
} |
1421
|
|
|
|
1422
|
|
|
return $index; |
1423
|
|
|
} |
1424
|
|
|
} |
1425
|
|
|
if (empty($this->ordered_items[$index])) { |
1426
|
|
|
return $this->index; |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
return $index; |
1430
|
|
|
} |
1431
|
|
|
|
1432
|
|
|
/** |
1433
|
|
|
* Gets item_id for the next element. |
1434
|
|
|
* |
1435
|
|
|
* @return int Next item (DB) ID |
1436
|
|
|
*/ |
1437
|
|
|
public function get_next_item_id() |
1438
|
|
|
{ |
1439
|
|
|
$new_index = $this->get_next_index(); |
1440
|
|
|
if (!empty($new_index)) { |
1441
|
|
|
if (isset($this->ordered_items[$new_index])) { |
1442
|
|
|
return $this->ordered_items[$new_index]; |
1443
|
|
|
} |
1444
|
|
|
} |
1445
|
|
|
|
1446
|
|
|
return 0; |
1447
|
|
|
} |
1448
|
|
|
|
1449
|
|
|
/** |
1450
|
|
|
* Returns the package type ('scorm','aicc','scorm2004','ppt'...). |
1451
|
|
|
* |
1452
|
|
|
* Generally, the package provided is in the form of a zip file, so the function |
1453
|
|
|
* has been written to test a zip file. If not a zip, the function will return the |
1454
|
|
|
* default return value: '' |
1455
|
|
|
* |
1456
|
|
|
* @param string $filePath the path to the file |
1457
|
|
|
* @param string $file_name the original name of the file |
1458
|
|
|
* |
1459
|
|
|
* @return string 'scorm','aicc','scorm2004','error-empty-package' |
1460
|
|
|
* if the package is empty, or '' if the package cannot be recognized |
1461
|
|
|
*/ |
1462
|
|
|
public static function getPackageType($filePath, $file_name) |
1463
|
|
|
{ |
1464
|
|
|
// Get name of the zip file without the extension. |
1465
|
|
|
$file_info = pathinfo($file_name); |
1466
|
|
|
$extension = $file_info['extension']; // Extension only. |
1467
|
|
|
if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [ |
1468
|
|
|
'dll', |
1469
|
|
|
'exe', |
1470
|
|
|
])) { |
1471
|
|
|
return 'oogie'; |
1472
|
|
|
} |
1473
|
|
|
if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [ |
1474
|
|
|
'dll', |
1475
|
|
|
'exe', |
1476
|
|
|
])) { |
1477
|
|
|
return 'woogie'; |
1478
|
|
|
} |
1479
|
|
|
|
1480
|
|
|
$zipFile = new ZipFile(); |
1481
|
|
|
$zipFile->openFile($filePath); |
1482
|
|
|
$zipContentArray = $zipFile->getEntries(); |
1483
|
|
|
$package_type = ''; |
1484
|
|
|
$manifest = ''; |
1485
|
|
|
$aicc_match_crs = 0; |
1486
|
|
|
$aicc_match_au = 0; |
1487
|
|
|
$aicc_match_des = 0; |
1488
|
|
|
$aicc_match_cst = 0; |
1489
|
|
|
$countItems = 0; |
1490
|
|
|
// The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?). |
1491
|
|
|
if ($zipContentArray) { |
1492
|
|
|
$countItems = count($zipContentArray); |
1493
|
|
|
if ($countItems > 0) { |
1494
|
|
|
foreach ($zipContentArray as $thisContent) { |
1495
|
|
|
$fileName = basename($thisContent->getName()); |
1496
|
|
|
if (preg_match('~.(php.*|phtml)$~i', $fileName)) { |
1497
|
|
|
// New behaviour: Don't do anything. These files will be removed in scorm::import_package. |
1498
|
|
|
} elseif (false !== stristr($fileName, 'imsmanifest.xml')) { |
1499
|
|
|
$manifest = $fileName; // Just the relative directory inside scorm/ |
1500
|
|
|
$package_type = 'scorm'; |
1501
|
|
|
break; // Exit the foreach loop. |
1502
|
|
|
} elseif ( |
1503
|
|
|
preg_match('/aicc\//i', $fileName) || |
1504
|
|
|
in_array( |
1505
|
|
|
strtolower(pathinfo($fileName, PATHINFO_EXTENSION)), |
1506
|
|
|
['crs', 'au', 'des', 'cst'] |
1507
|
|
|
) |
1508
|
|
|
) { |
1509
|
|
|
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); |
1510
|
|
|
switch ($ext) { |
1511
|
|
|
case 'crs': |
1512
|
|
|
$aicc_match_crs = 1; |
1513
|
|
|
break; |
1514
|
|
|
case 'au': |
1515
|
|
|
$aicc_match_au = 1; |
1516
|
|
|
break; |
1517
|
|
|
case 'des': |
1518
|
|
|
$aicc_match_des = 1; |
1519
|
|
|
break; |
1520
|
|
|
case 'cst': |
1521
|
|
|
$aicc_match_cst = 1; |
1522
|
|
|
break; |
1523
|
|
|
default: |
1524
|
|
|
break; |
1525
|
|
|
} |
1526
|
|
|
//break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC. |
1527
|
|
|
} else { |
1528
|
|
|
$package_type = ''; |
1529
|
|
|
} |
1530
|
|
|
} |
1531
|
|
|
} |
1532
|
|
|
} |
1533
|
|
|
|
1534
|
|
|
if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) { |
1535
|
|
|
// If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)). |
1536
|
|
|
$package_type = 'aicc'; |
1537
|
|
|
} |
1538
|
|
|
|
1539
|
|
|
// Try with chamilo course builder |
1540
|
|
|
if (empty($package_type)) { |
1541
|
|
|
// Sometimes users will try to upload an empty zip, or a zip with |
1542
|
|
|
// only a folder. Catch that and make the calling function aware. |
1543
|
|
|
// If the single file was the imsmanifest.xml, then $package_type |
1544
|
|
|
// would be 'scorm' and we wouldn't be here. |
1545
|
|
|
if ($countItems < 2) { |
1546
|
|
|
return 'error-empty-package'; |
1547
|
|
|
} |
1548
|
|
|
$package_type = 'chamilo'; |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
return $package_type; |
1552
|
|
|
} |
1553
|
|
|
|
1554
|
|
|
/** |
1555
|
|
|
* Gets the previous resource in queue (url). Also initialises time values for this viewing. |
1556
|
|
|
* |
1557
|
|
|
* @return string URL to load into the viewer |
1558
|
|
|
*/ |
1559
|
|
|
public function get_previous_index() |
1560
|
|
|
{ |
1561
|
|
|
$index = $this->index; |
1562
|
|
|
if (isset($this->ordered_items[$index - 1])) { |
1563
|
|
|
$index--; |
1564
|
|
|
while (isset($this->ordered_items[$index]) && |
1565
|
|
|
('dir' === $this->items[$this->ordered_items[$index]]->get_type()) |
1566
|
|
|
) { |
1567
|
|
|
$index--; |
1568
|
|
|
if ($index < 0) { |
1569
|
|
|
return $this->index; |
1570
|
|
|
} |
1571
|
|
|
} |
1572
|
|
|
} |
1573
|
|
|
|
1574
|
|
|
return $index; |
1575
|
|
|
} |
1576
|
|
|
|
1577
|
|
|
/** |
1578
|
|
|
* Gets item_id for the next element. |
1579
|
|
|
* |
1580
|
|
|
* @return int Previous item (DB) ID |
1581
|
|
|
*/ |
1582
|
|
|
public function get_previous_item_id() |
1583
|
|
|
{ |
1584
|
|
|
$index = $this->get_previous_index(); |
1585
|
|
|
|
1586
|
|
|
return $this->ordered_items[$index]; |
1587
|
|
|
} |
1588
|
|
|
|
1589
|
|
|
/** |
1590
|
|
|
* Returns the HTML necessary to print a mediaplayer block inside a page. |
1591
|
|
|
* |
1592
|
|
|
* @param int $lpItemId |
1593
|
|
|
* @param string $autostart |
1594
|
|
|
* |
1595
|
|
|
* @return string The mediaplayer HTML |
1596
|
|
|
*/ |
1597
|
|
|
public function get_mediaplayer($lpItemId, $autostart = 'true') |
1598
|
|
|
{ |
1599
|
|
|
$courseInfo = api_get_course_info(); |
1600
|
|
|
$lpItemId = (int) $lpItemId; |
1601
|
|
|
|
1602
|
|
|
if (empty($courseInfo) || empty($lpItemId)) { |
1603
|
|
|
return ''; |
1604
|
|
|
} |
1605
|
|
|
$item = $this->items[$lpItemId] ?? null; |
1606
|
|
|
|
1607
|
|
|
if (empty($item)) { |
1608
|
|
|
return ''; |
1609
|
|
|
} |
1610
|
|
|
|
1611
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
1612
|
|
|
$tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); |
1613
|
|
|
$itemViewId = (int) $item->db_item_view_id; |
1614
|
|
|
|
1615
|
|
|
// Getting all the information about the item. |
1616
|
|
|
$sql = "SELECT lp_view.status |
1617
|
|
|
FROM $tbl_lp_item as lpi |
1618
|
|
|
INNER JOIN $tbl_lp_item_view as lp_view |
1619
|
|
|
ON (lpi.iid = lp_view.lp_item_id) |
1620
|
|
|
WHERE |
1621
|
|
|
lp_view.iid = $itemViewId AND |
1622
|
|
|
lpi.iid = $lpItemId |
1623
|
|
|
"; |
1624
|
|
|
$result = Database::query($sql); |
1625
|
|
|
$row = Database::fetch_assoc($result); |
1626
|
|
|
$output = ''; |
1627
|
|
|
$audio = $item->audio; |
1628
|
|
|
|
1629
|
|
|
if (!empty($audio)) { |
1630
|
|
|
$list = $_SESSION['oLP']->get_toc(); |
1631
|
|
|
|
1632
|
|
|
switch ($item->get_type()) { |
1633
|
|
|
case 'quiz': |
1634
|
|
|
$type_quiz = false; |
1635
|
|
|
foreach ($list as $toc) { |
1636
|
|
|
if ($toc['id'] == $_SESSION['oLP']->current) { |
1637
|
|
|
$type_quiz = true; |
1638
|
|
|
} |
1639
|
|
|
} |
1640
|
|
|
|
1641
|
|
|
if ($type_quiz) { |
1642
|
|
|
if (1 == $_SESSION['oLP']->prevent_reinit) { |
1643
|
|
|
$autostart_audio = 'completed' === $row['status'] ? 'false' : 'true'; |
1644
|
|
|
} else { |
1645
|
|
|
$autostart_audio = $autostart; |
1646
|
|
|
} |
1647
|
|
|
} |
1648
|
|
|
break; |
1649
|
|
|
case TOOL_READOUT_TEXT: |
1650
|
|
|
$autostart_audio = 'false'; |
1651
|
|
|
break; |
1652
|
|
|
default: |
1653
|
|
|
$autostart_audio = 'true'; |
1654
|
|
|
} |
1655
|
|
|
|
1656
|
|
|
$file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio; |
1657
|
|
|
$url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq(); |
1658
|
|
|
|
1659
|
|
|
$player = Display::getMediaPlayer( |
1660
|
|
|
$file, |
1661
|
|
|
[ |
1662
|
|
|
'id' => 'lp_audio_media_player', |
1663
|
|
|
'url' => $url, |
1664
|
|
|
'autoplay' => $autostart_audio, |
1665
|
|
|
'width' => '100%', |
1666
|
|
|
] |
1667
|
|
|
); |
1668
|
|
|
|
1669
|
|
|
// The mp3 player. |
1670
|
|
|
$output = '<div id="container">'; |
1671
|
|
|
$output .= $player; |
1672
|
|
|
$output .= '</div>'; |
1673
|
|
|
} |
1674
|
|
|
|
1675
|
|
|
return $output; |
1676
|
|
|
} |
1677
|
|
|
|
1678
|
|
|
/** |
1679
|
|
|
* @param int $studentId |
1680
|
|
|
* @param int $prerequisite |
1681
|
|
|
* @param Course $course |
1682
|
|
|
* @param int $sessionId |
1683
|
|
|
* |
1684
|
|
|
* @return bool |
1685
|
|
|
*/ |
1686
|
|
|
public static function isBlockedByPrerequisite( |
1687
|
|
|
$studentId, |
1688
|
|
|
$prerequisite, |
1689
|
|
|
Course $course, |
1690
|
|
|
$sessionId |
1691
|
|
|
) { |
1692
|
|
|
$courseId = $course->getId(); |
1693
|
|
|
|
1694
|
|
|
$allow = ('true' === api_get_setting('lp.allow_teachers_to_access_blocked_lp_by_prerequisite')); |
1695
|
|
|
if ($allow) { |
1696
|
|
|
if (api_is_allowed_to_edit() || |
1697
|
|
|
api_is_platform_admin(true) || |
1698
|
|
|
api_is_drh() || |
1699
|
|
|
api_is_coach($sessionId, $courseId, false) |
1700
|
|
|
) { |
1701
|
|
|
return false; |
1702
|
|
|
} |
1703
|
|
|
} |
1704
|
|
|
|
1705
|
|
|
$isBlocked = false; |
1706
|
|
|
if (!empty($prerequisite)) { |
1707
|
|
|
$progress = self::getProgress( |
1708
|
|
|
$prerequisite, |
1709
|
|
|
$studentId, |
1710
|
|
|
$courseId, |
1711
|
|
|
$sessionId |
1712
|
|
|
); |
1713
|
|
|
if ($progress < 100) { |
1714
|
|
|
$isBlocked = true; |
1715
|
|
|
} |
1716
|
|
|
|
1717
|
|
|
if (Tracking::minimumTimeAvailable($sessionId, $courseId)) { |
1718
|
|
|
// Block if it does not exceed minimum time |
1719
|
|
|
// Minimum time (in minutes) to pass the learning path |
1720
|
|
|
$accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId); |
1721
|
|
|
|
1722
|
|
|
if ($accumulateWorkTime > 0) { |
1723
|
|
|
// Total time in course (sum of times in learning paths from course) |
1724
|
|
|
$accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId); |
1725
|
|
|
|
1726
|
|
|
// Connect with the plugin_licences_course_session table |
1727
|
|
|
// which indicates what percentage of the time applies |
1728
|
|
|
// Minimum connection percentage |
1729
|
|
|
$perc = 100; |
1730
|
|
|
// Time from the course |
1731
|
|
|
$tc = $accumulateWorkTimeTotal; |
1732
|
|
|
|
1733
|
|
|
// Percentage of the learning paths |
1734
|
|
|
$pl = $accumulateWorkTime / $accumulateWorkTimeTotal; |
1735
|
|
|
// Minimum time for each learning path |
1736
|
|
|
$accumulateWorkTime = ($pl * $tc * $perc / 100); |
1737
|
|
|
|
1738
|
|
|
// Spent time (in seconds) so far in the learning path |
1739
|
|
|
$lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId); |
1740
|
|
|
$lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0; |
1741
|
|
|
|
1742
|
|
|
if ($lpTime < ($accumulateWorkTime * 60)) { |
1743
|
|
|
$isBlocked = true; |
1744
|
|
|
} |
1745
|
|
|
} |
1746
|
|
|
} |
1747
|
|
|
} |
1748
|
|
|
|
1749
|
|
|
return $isBlocked; |
1750
|
|
|
} |
1751
|
|
|
|
1752
|
|
|
/** |
1753
|
|
|
* Checks if the learning path is visible for student after the progress |
1754
|
|
|
* of its prerequisite is completed, considering the time availability and |
1755
|
|
|
* the LP visibility. |
1756
|
|
|
*/ |
1757
|
|
|
public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool |
1758
|
|
|
{ |
1759
|
|
|
$sessionId = $session ? $session->getId() : 0; |
1760
|
|
|
$courseId = $course->getId(); |
1761
|
|
|
$visibility = $lp->isVisible($course, $session); |
1762
|
|
|
|
1763
|
|
|
// If the item was deleted. |
1764
|
|
|
if (false === $visibility) { |
1765
|
|
|
return false; |
1766
|
|
|
} |
1767
|
|
|
|
1768
|
|
|
$now = time(); |
1769
|
|
|
if ($lp->hasCategory()) { |
1770
|
|
|
$category = $lp->getCategory(); |
1771
|
|
|
|
1772
|
|
|
if (false === self::categoryIsVisibleForStudent( |
1773
|
|
|
$category, |
1774
|
|
|
api_get_user_entity($student_id), |
1775
|
|
|
$course, |
1776
|
|
|
$session |
1777
|
|
|
)) { |
1778
|
|
|
return false; |
1779
|
|
|
} |
1780
|
|
|
|
1781
|
|
|
$prerequisite = $lp->getPrerequisite(); |
1782
|
|
|
$is_visible = true; |
1783
|
|
|
|
1784
|
|
|
$isBlocked = self::isBlockedByPrerequisite( |
1785
|
|
|
$student_id, |
1786
|
|
|
$prerequisite, |
1787
|
|
|
$course, |
1788
|
|
|
$sessionId |
1789
|
|
|
); |
1790
|
|
|
|
1791
|
|
|
if ($isBlocked) { |
1792
|
|
|
$is_visible = false; |
1793
|
|
|
} |
1794
|
|
|
|
1795
|
|
|
// Also check the time availability of the LP |
1796
|
|
|
if ($is_visible) { |
1797
|
|
|
// Adding visibility restrictions |
1798
|
|
|
if (null !== $lp->getPublishedOn()) { |
1799
|
|
|
if ($now < $lp->getPublishedOn()->getTimestamp()) { |
1800
|
|
|
$is_visible = false; |
1801
|
|
|
} |
1802
|
|
|
} |
1803
|
|
|
// Blocking empty start times see BT#2800 |
1804
|
|
|
global $_custom; |
1805
|
|
|
if (isset($_custom['lps_hidden_when_no_start_date']) && |
1806
|
|
|
$_custom['lps_hidden_when_no_start_date'] |
1807
|
|
|
) { |
1808
|
|
|
if (null !== $lp->getPublishedOn()) { |
1809
|
|
|
$is_visible = false; |
1810
|
|
|
} |
1811
|
|
|
} |
1812
|
|
|
|
1813
|
|
|
if (null !== $lp->getExpiredOn()) { |
1814
|
|
|
if ($now > $lp->getExpiredOn()->getTimestamp()) { |
1815
|
|
|
$is_visible = false; |
1816
|
|
|
} |
1817
|
|
|
} |
1818
|
|
|
} |
1819
|
|
|
|
1820
|
|
|
if ($is_visible) { |
1821
|
|
|
$subscriptionSettings = self::getSubscriptionSettings(); |
1822
|
|
|
|
1823
|
|
|
// Check if the subscription users/group to a LP is ON |
1824
|
|
|
if (1 == $lp->getSubscribeUsers() && |
1825
|
|
|
true === $subscriptionSettings['allow_add_users_to_lp'] |
1826
|
|
|
) { |
1827
|
|
|
// Try group |
1828
|
|
|
$is_visible = false; |
1829
|
|
|
// Checking only the user visibility |
1830
|
|
|
$userVisibility = self::isUserSubscribedToLp($lp, $student_id, $course, $session); |
1831
|
|
|
|
1832
|
|
|
if (true === $userVisibility) { |
1833
|
|
|
return true; |
1834
|
|
|
} |
1835
|
|
|
|
1836
|
|
|
// Try with groups |
1837
|
|
|
$groupVisibility = self::isGroupSubscribedToLp($lp, $student_id, $course, $session); |
1838
|
|
|
if (true === $groupVisibility) { |
1839
|
|
|
return true; |
1840
|
|
|
} |
1841
|
|
|
} |
1842
|
|
|
} |
1843
|
|
|
|
1844
|
|
|
return $is_visible; |
1845
|
|
|
} else { |
1846
|
|
|
|
1847
|
|
|
$is_visible = true; |
1848
|
|
|
$subscriptionSettings = self::getSubscriptionSettings(); |
1849
|
|
|
// Check if the subscription users/group to a LP is ON |
1850
|
|
|
if (1 == $lp->getSubscribeUsers() && |
1851
|
|
|
true === $subscriptionSettings['allow_add_users_to_lp'] |
1852
|
|
|
) { |
1853
|
|
|
$is_visible = false; |
1854
|
|
|
$userVisibility = self::isUserSubscribedToLp($lp, $student_id, $course, $session); |
1855
|
|
|
|
1856
|
|
|
if (true === $userVisibility) { |
1857
|
|
|
return true; |
1858
|
|
|
} |
1859
|
|
|
|
1860
|
|
|
// Try with groups |
1861
|
|
|
$groupVisibility = self::isGroupSubscribedToLp($lp, $student_id, $course, $session); |
1862
|
|
|
if (true === $groupVisibility) { |
1863
|
|
|
return true; |
1864
|
|
|
} |
1865
|
|
|
} |
1866
|
|
|
|
1867
|
|
|
return $is_visible; |
1868
|
|
|
} |
1869
|
|
|
|
1870
|
|
|
return true; |
|
|
|
|
1871
|
|
|
} |
1872
|
|
|
|
1873
|
|
|
public static function isGroupSubscribedToLp( |
1874
|
|
|
CLp $lp, |
1875
|
|
|
int $studentId, |
1876
|
|
|
Course $course, |
1877
|
|
|
SessionEntity $session = null |
1878
|
|
|
): bool { |
1879
|
|
|
|
1880
|
|
|
// Subscribed groups to a LP |
1881
|
|
|
$links = $lp->getResourceNode()->getResourceLinks(); |
1882
|
|
|
$selectedChoices = []; |
1883
|
|
|
foreach ($links as $link) { |
1884
|
|
|
if (null !== $link->getGroup()) { |
1885
|
|
|
$selectedChoices[] = $link->getGroup()->getIid(); |
1886
|
|
|
} |
1887
|
|
|
} |
1888
|
|
|
|
1889
|
|
|
$isVisible = false; |
1890
|
|
|
$userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $course->getId()); |
1891
|
|
|
if (!empty($userGroups)) { |
1892
|
|
|
foreach ($userGroups as $groupInfo) { |
1893
|
|
|
$groupId = $groupInfo['iid']; |
1894
|
|
|
if (in_array($groupId, $selectedChoices)) { |
1895
|
|
|
$isVisible = true; |
1896
|
|
|
break; |
1897
|
|
|
} |
1898
|
|
|
} |
1899
|
|
|
} |
1900
|
|
|
|
1901
|
|
|
return $isVisible; |
1902
|
|
|
} |
1903
|
|
|
|
1904
|
|
|
public static function isUserSubscribedToLp( |
1905
|
|
|
CLp $lp, |
1906
|
|
|
int $studentId, |
1907
|
|
|
Course $course, |
1908
|
|
|
SessionEntity $session = null |
1909
|
|
|
): bool { |
1910
|
|
|
|
1911
|
|
|
$isVisible = true; |
1912
|
|
|
$em = Database::getManager(); |
1913
|
|
|
|
1914
|
|
|
/** @var CLpRelUserRepository $cLpRelUserRepo */ |
1915
|
|
|
$cLpRelUserRepo = $em->getRepository(CLpRelUser::class); |
1916
|
|
|
|
1917
|
|
|
// Getting subscribed users to a LP. |
1918
|
|
|
$subscribedUsersInLp = $cLpRelUserRepo->getUsersSubscribedToItem( |
1919
|
|
|
$lp, |
1920
|
|
|
$course, |
1921
|
|
|
$session |
1922
|
|
|
); |
1923
|
|
|
|
1924
|
|
|
$selectedChoices = []; |
1925
|
|
|
foreach ($subscribedUsersInLp as $users) { |
1926
|
|
|
/** @var \Chamilo\CourseBundle\Entity\CLpRelUser $users */ |
1927
|
|
|
$selectedChoices[] = $users->getUser()->getId(); |
1928
|
|
|
} |
1929
|
|
|
|
1930
|
|
|
if (!api_is_allowed_to_edit() && !in_array($studentId, $selectedChoices)) { |
1931
|
|
|
$isVisible = false; |
1932
|
|
|
} |
1933
|
|
|
|
1934
|
|
|
return $isVisible; |
1935
|
|
|
} |
1936
|
|
|
|
1937
|
|
|
/** |
1938
|
|
|
* @param int $lpId |
1939
|
|
|
* @param int $userId |
1940
|
|
|
* @param int $courseId |
1941
|
|
|
* @param int $sessionId |
1942
|
|
|
* |
1943
|
|
|
* @return int |
1944
|
|
|
*/ |
1945
|
|
|
public static function getProgress($lpId, $userId, $courseId, $sessionId = 0) |
1946
|
|
|
{ |
1947
|
|
|
$lpId = (int) $lpId; |
1948
|
|
|
$userId = (int) $userId; |
1949
|
|
|
$courseId = (int) $courseId; |
1950
|
|
|
$sessionId = (int) $sessionId; |
1951
|
|
|
|
1952
|
|
|
$sessionCondition = api_get_session_condition($sessionId); |
1953
|
|
|
$table = Database::get_course_table(TABLE_LP_VIEW); |
1954
|
|
|
$sql = "SELECT progress FROM $table |
1955
|
|
|
WHERE |
1956
|
|
|
c_id = $courseId AND |
1957
|
|
|
lp_id = $lpId AND |
1958
|
|
|
user_id = $userId $sessionCondition "; |
1959
|
|
|
$res = Database::query($sql); |
1960
|
|
|
|
1961
|
|
|
$progress = 0; |
1962
|
|
|
if (Database::num_rows($res) > 0) { |
1963
|
|
|
$row = Database::fetch_array($res); |
1964
|
|
|
$progress = (int) $row['progress']; |
1965
|
|
|
} |
1966
|
|
|
|
1967
|
|
|
return $progress; |
1968
|
|
|
} |
1969
|
|
|
|
1970
|
|
|
/** |
1971
|
|
|
* @param array $lpList |
1972
|
|
|
* @param int $userId |
1973
|
|
|
* @param int $courseId |
1974
|
|
|
* @param int $sessionId |
1975
|
|
|
* |
1976
|
|
|
* @return array |
1977
|
|
|
*/ |
1978
|
|
|
public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0) |
1979
|
|
|
{ |
1980
|
|
|
$lpList = array_map('intval', $lpList); |
1981
|
|
|
if (empty($lpList)) { |
1982
|
|
|
return []; |
1983
|
|
|
} |
1984
|
|
|
|
1985
|
|
|
$lpList = implode("','", $lpList); |
1986
|
|
|
|
1987
|
|
|
$userId = (int) $userId; |
1988
|
|
|
$courseId = (int) $courseId; |
1989
|
|
|
$sessionId = (int) $sessionId; |
1990
|
|
|
|
1991
|
|
|
$sessionCondition = api_get_session_condition($sessionId); |
1992
|
|
|
$table = Database::get_course_table(TABLE_LP_VIEW); |
1993
|
|
|
$sql = "SELECT lp_id, progress FROM $table |
1994
|
|
|
WHERE |
1995
|
|
|
c_id = $courseId AND |
1996
|
|
|
lp_id IN ('".$lpList."') AND |
1997
|
|
|
user_id = $userId $sessionCondition "; |
1998
|
|
|
$res = Database::query($sql); |
1999
|
|
|
|
2000
|
|
|
if (Database::num_rows($res) > 0) { |
2001
|
|
|
$list = []; |
2002
|
|
|
while ($row = Database::fetch_array($res)) { |
2003
|
|
|
$list[$row['lp_id']] = $row['progress']; |
2004
|
|
|
} |
2005
|
|
|
|
2006
|
|
|
return $list; |
2007
|
|
|
} |
2008
|
|
|
|
2009
|
|
|
return []; |
2010
|
|
|
} |
2011
|
|
|
|
2012
|
|
|
/** |
2013
|
|
|
* Displays a progress bar |
2014
|
|
|
* completed so far. |
2015
|
|
|
* |
2016
|
|
|
* @param int $percentage Progress value to display |
2017
|
|
|
* @param string $text_add Text to display near the progress value |
2018
|
|
|
* |
2019
|
|
|
* @return string HTML string containing the progress bar |
2020
|
|
|
*/ |
2021
|
|
|
public static function get_progress_bar($percentage = -1, $text_add = '') |
2022
|
|
|
{ |
2023
|
|
|
$text = $percentage.$text_add; |
2024
|
|
|
|
2025
|
|
|
return '<div class="p-progressbar p-progressbar-determinate" |
2026
|
|
|
role="progressbar" aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100"> |
2027
|
|
|
<div id="progress_bar_value" class="p-progressbar-value" style="width: '.$text.';"> |
2028
|
|
|
<div class="p-progressbar-label">'.$text.'</div> |
2029
|
|
|
</div> |
2030
|
|
|
</div>'; |
2031
|
|
|
} |
2032
|
|
|
|
2033
|
|
|
/** |
2034
|
|
|
* @param string $mode can be '%' or 'abs' |
2035
|
|
|
* otherwise this value will be used $this->progress_bar_mode |
2036
|
|
|
* |
2037
|
|
|
* @return string |
2038
|
|
|
*/ |
2039
|
|
|
public function getProgressBar($mode = null) |
2040
|
|
|
{ |
2041
|
|
|
[$percentage, $text_add] = $this->get_progress_bar_text($mode); |
2042
|
|
|
|
2043
|
|
|
return self::get_progress_bar($percentage, $text_add); |
2044
|
|
|
} |
2045
|
|
|
|
2046
|
|
|
/** |
2047
|
|
|
* Gets the progress bar info to display inside the progress bar. |
2048
|
|
|
* Also used by scorm_api.php. |
2049
|
|
|
* |
2050
|
|
|
* @param string $mode Mode of display (can be '%' or 'abs').abs means |
2051
|
|
|
* we display a number of completed elements per total elements |
2052
|
|
|
* @param int $add Additional steps to fake as completed |
2053
|
|
|
* |
2054
|
|
|
* @return array Percentage or number and symbol (% or /xx) |
2055
|
|
|
*/ |
2056
|
|
|
public function get_progress_bar_text($mode = '', $add = 0) |
2057
|
|
|
{ |
2058
|
|
|
if (empty($mode)) { |
2059
|
|
|
$mode = $this->progress_bar_mode; |
2060
|
|
|
} |
2061
|
|
|
$text = ''; |
2062
|
|
|
$percentage = 0; |
2063
|
|
|
// If the option to use the score as progress is set for this learning |
2064
|
|
|
// path, then the rules are completely different: we assume only one |
2065
|
|
|
// item exists and the progress of the LP depends on the score |
2066
|
|
|
$scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable')); |
2067
|
|
|
if (true === $scoreAsProgressSetting) { |
2068
|
|
|
$scoreAsProgress = $this->getUseScoreAsProgress(); |
2069
|
|
|
if ($scoreAsProgress) { |
2070
|
|
|
// Get single item's score |
2071
|
|
|
$itemId = $this->get_current_item_id(); |
2072
|
|
|
$item = $this->getItem($itemId); |
2073
|
|
|
$score = $item->get_score(); |
2074
|
|
|
$maxScore = $item->get_max(); |
2075
|
|
|
if ($mode = '%') { |
2076
|
|
|
if (!empty($maxScore)) { |
2077
|
|
|
$percentage = ((float) $score / (float) $maxScore) * 100; |
2078
|
|
|
} |
2079
|
|
|
$percentage = number_format($percentage, 0); |
2080
|
|
|
$text = '%'; |
2081
|
|
|
} else { |
2082
|
|
|
$percentage = $score; |
2083
|
|
|
$text = '/'.$maxScore; |
2084
|
|
|
} |
2085
|
|
|
|
2086
|
|
|
return [$percentage, $text]; |
2087
|
|
|
} |
2088
|
|
|
} |
2089
|
|
|
// otherwise just continue the normal processing of progress |
2090
|
|
|
$total_items = $this->getTotalItemsCountWithoutDirs(); |
2091
|
|
|
$completeItems = $this->get_complete_items_count(); |
2092
|
|
|
if (0 != $add) { |
2093
|
|
|
$completeItems += $add; |
2094
|
|
|
} |
2095
|
|
|
if ($completeItems > $total_items) { |
2096
|
|
|
$completeItems = $total_items; |
2097
|
|
|
} |
2098
|
|
|
if ('%' === $mode) { |
2099
|
|
|
if ($total_items > 0) { |
2100
|
|
|
$percentage = ((float) $completeItems / (float) $total_items) * 100; |
2101
|
|
|
} |
2102
|
|
|
$percentage = number_format($percentage, 0); |
2103
|
|
|
$text = '%'; |
2104
|
|
|
} elseif ('abs' === $mode) { |
2105
|
|
|
$percentage = $completeItems; |
2106
|
|
|
$text = '/'.$total_items; |
2107
|
|
|
} |
2108
|
|
|
|
2109
|
|
|
return [ |
2110
|
|
|
$percentage, |
2111
|
|
|
$text, |
2112
|
|
|
]; |
2113
|
|
|
} |
2114
|
|
|
|
2115
|
|
|
/** |
2116
|
|
|
* Gets the progress bar mode. |
2117
|
|
|
* |
2118
|
|
|
* @return string The progress bar mode attribute |
2119
|
|
|
*/ |
2120
|
|
|
public function get_progress_bar_mode() |
2121
|
|
|
{ |
2122
|
|
|
if (!empty($this->progress_bar_mode)) { |
2123
|
|
|
return $this->progress_bar_mode; |
2124
|
|
|
} |
2125
|
|
|
|
2126
|
|
|
return '%'; |
2127
|
|
|
} |
2128
|
|
|
|
2129
|
|
|
/** |
2130
|
|
|
* Gets the learnpath theme (remote or local). |
2131
|
|
|
* |
2132
|
|
|
* @return string Learnpath theme |
2133
|
|
|
*/ |
2134
|
|
|
public function get_theme() |
2135
|
|
|
{ |
2136
|
|
|
if (!empty($this->theme)) { |
2137
|
|
|
return $this->theme; |
2138
|
|
|
} |
2139
|
|
|
|
2140
|
|
|
return ''; |
2141
|
|
|
} |
2142
|
|
|
|
2143
|
|
|
/** |
2144
|
|
|
* Gets the learnpath session id. |
2145
|
|
|
* |
2146
|
|
|
* @return int |
2147
|
|
|
*/ |
2148
|
|
|
public function get_lp_session_id() |
2149
|
|
|
{ |
2150
|
|
|
$lp = Container::getLpRepository()->find($this->lp_id); |
2151
|
|
|
if ($lp) { |
2152
|
|
|
/* @var ResourceNode $resourceNode */ |
2153
|
|
|
$resourceNode = $lp->getResourceNode(); |
2154
|
|
|
if ($resourceNode) { |
|
|
|
|
2155
|
|
|
$link = $resourceNode->getResourceLinks()->first(); |
2156
|
|
|
if ($link && $link->getSession()) { |
2157
|
|
|
|
2158
|
|
|
return (int) $link->getSession()->getId(); |
2159
|
|
|
} |
2160
|
|
|
} |
2161
|
|
|
} |
2162
|
|
|
|
2163
|
|
|
return 0; |
2164
|
|
|
} |
2165
|
|
|
|
2166
|
|
|
/** |
2167
|
|
|
* Generate a new prerequisites string for a given item. If this item was a sco and |
2168
|
|
|
* its prerequisites were strings (instead of IDs), then transform those strings into |
2169
|
|
|
* IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table. |
2170
|
|
|
* Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the |
2171
|
|
|
* same rule as the scormExport() method. |
2172
|
|
|
* |
2173
|
|
|
* @param int $item_id Item ID |
2174
|
|
|
* |
2175
|
|
|
* @return string Prerequisites string ready for the export as SCORM |
2176
|
|
|
*/ |
2177
|
|
|
public function get_scorm_prereq_string($item_id) |
2178
|
|
|
{ |
2179
|
|
|
if ($this->debug > 0) { |
2180
|
|
|
error_log('In learnpath::get_scorm_prereq_string()'); |
2181
|
|
|
} |
2182
|
|
|
if (!is_object($this->items[$item_id])) { |
2183
|
|
|
return false; |
2184
|
|
|
} |
2185
|
|
|
/** @var learnpathItem $oItem */ |
2186
|
|
|
$oItem = $this->items[$item_id]; |
2187
|
|
|
$prereq = $oItem->get_prereq_string(); |
2188
|
|
|
|
2189
|
|
|
if (empty($prereq)) { |
2190
|
|
|
return ''; |
2191
|
|
|
} |
2192
|
|
|
if (preg_match('/^\d+$/', $prereq) && |
2193
|
|
|
isset($this->items[$prereq]) && |
2194
|
|
|
is_object($this->items[$prereq]) |
2195
|
|
|
) { |
2196
|
|
|
// If the prerequisite is a simple integer ID and this ID exists as an item ID, |
2197
|
|
|
// then simply return it (with the ITEM_ prefix). |
2198
|
|
|
//return 'ITEM_' . $prereq; |
2199
|
|
|
return $this->items[$prereq]->ref; |
2200
|
|
|
} else { |
2201
|
|
|
if (isset($this->refs_list[$prereq])) { |
2202
|
|
|
// It's a simple string item from which the ID can be found in the refs list, |
2203
|
|
|
// so we can transform it directly to an ID for export. |
2204
|
|
|
return $this->items[$this->refs_list[$prereq]]->ref; |
2205
|
|
|
} elseif (isset($this->refs_list['ITEM_'.$prereq])) { |
2206
|
|
|
return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref; |
2207
|
|
|
} else { |
2208
|
|
|
// The last case, if it's a complex form, then find all the IDs (SCORM strings) |
2209
|
|
|
// and replace them, one by one, by the internal IDs (chamilo db) |
2210
|
|
|
// TODO: Modify the '*' replacement to replace the multiplier in front of it |
2211
|
|
|
// by a space as well. |
2212
|
|
|
$find = [ |
2213
|
|
|
'&', |
2214
|
|
|
'|', |
2215
|
|
|
'~', |
2216
|
|
|
'=', |
2217
|
|
|
'<>', |
2218
|
|
|
'{', |
2219
|
|
|
'}', |
2220
|
|
|
'*', |
2221
|
|
|
'(', |
2222
|
|
|
')', |
2223
|
|
|
]; |
2224
|
|
|
$replace = [ |
2225
|
|
|
' ', |
2226
|
|
|
' ', |
2227
|
|
|
' ', |
2228
|
|
|
' ', |
2229
|
|
|
' ', |
2230
|
|
|
' ', |
2231
|
|
|
' ', |
2232
|
|
|
' ', |
2233
|
|
|
' ', |
2234
|
|
|
' ', |
2235
|
|
|
]; |
2236
|
|
|
$prereq_mod = str_replace($find, $replace, $prereq); |
2237
|
|
|
$ids = explode(' ', $prereq_mod); |
2238
|
|
|
foreach ($ids as $id) { |
2239
|
|
|
$id = trim($id); |
2240
|
|
|
if (isset($this->refs_list[$id])) { |
2241
|
|
|
$prereq = preg_replace( |
2242
|
|
|
'/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/', |
2243
|
|
|
'ITEM_'.$this->refs_list[$id], |
2244
|
|
|
$prereq |
2245
|
|
|
); |
2246
|
|
|
} |
2247
|
|
|
} |
2248
|
|
|
|
2249
|
|
|
return $prereq; |
2250
|
|
|
} |
2251
|
|
|
} |
2252
|
|
|
} |
2253
|
|
|
|
2254
|
|
|
/** |
2255
|
|
|
* Returns the XML DOM document's node. |
2256
|
|
|
* |
2257
|
|
|
* @param resource $children Reference to a list of objects to search for the given ITEM_* |
2258
|
|
|
* @param string $id The identifier to look for |
2259
|
|
|
* |
2260
|
|
|
* @return mixed The reference to the element found with that identifier. False if not found |
2261
|
|
|
*/ |
2262
|
|
|
public function get_scorm_xml_node(&$children, $id) |
2263
|
|
|
{ |
2264
|
|
|
for ($i = 0; $i < $children->length; $i++) { |
2265
|
|
|
$item_temp = $children->item($i); |
2266
|
|
|
if ('item' === $item_temp->nodeName) { |
2267
|
|
|
if ($item_temp->getAttribute('identifier') == $id) { |
2268
|
|
|
return $item_temp; |
2269
|
|
|
} |
2270
|
|
|
} |
2271
|
|
|
$subchildren = $item_temp->childNodes; |
2272
|
|
|
if ($subchildren && $subchildren->length > 0) { |
2273
|
|
|
$val = $this->get_scorm_xml_node($subchildren, $id); |
2274
|
|
|
if (is_object($val)) { |
2275
|
|
|
return $val; |
2276
|
|
|
} |
2277
|
|
|
} |
2278
|
|
|
} |
2279
|
|
|
|
2280
|
|
|
return false; |
2281
|
|
|
} |
2282
|
|
|
|
2283
|
|
|
/** |
2284
|
|
|
* Gets the status list for all LP's items. |
2285
|
|
|
* |
2286
|
|
|
* @return array Array of [index] => [item ID => current status] |
2287
|
|
|
*/ |
2288
|
|
|
public function get_items_status_list() |
2289
|
|
|
{ |
2290
|
|
|
$list = []; |
2291
|
|
|
foreach ($this->ordered_items as $item_id) { |
2292
|
|
|
$list[] = [ |
2293
|
|
|
$item_id => $this->items[$item_id]->get_status(), |
2294
|
|
|
]; |
2295
|
|
|
} |
2296
|
|
|
|
2297
|
|
|
return $list; |
2298
|
|
|
} |
2299
|
|
|
|
2300
|
|
|
/** |
2301
|
|
|
* Return the number of interactions for the given learnpath Item View ID. |
2302
|
|
|
* This method can be used as static. |
2303
|
|
|
* |
2304
|
|
|
* @param int $lp_iv_id Item View ID |
2305
|
|
|
* @param int $course_id course id |
2306
|
|
|
* |
2307
|
|
|
* @return int |
2308
|
|
|
*/ |
2309
|
|
|
public static function get_interactions_count_from_db($lp_iv_id, $course_id) |
2310
|
|
|
{ |
2311
|
|
|
$table = Database::get_course_table(TABLE_LP_IV_INTERACTION); |
2312
|
|
|
$lp_iv_id = (int) $lp_iv_id; |
2313
|
|
|
$course_id = (int) $course_id; |
2314
|
|
|
|
2315
|
|
|
$sql = "SELECT count(*) FROM $table |
2316
|
|
|
WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id"; |
2317
|
|
|
$res = Database::query($sql); |
2318
|
|
|
$num = 0; |
2319
|
|
|
if (Database::num_rows($res)) { |
2320
|
|
|
$row = Database::fetch_array($res); |
2321
|
|
|
$num = $row[0]; |
2322
|
|
|
} |
2323
|
|
|
|
2324
|
|
|
return $num; |
2325
|
|
|
} |
2326
|
|
|
|
2327
|
|
|
/** |
2328
|
|
|
* Return the interactions as an array for the given lp_iv_id. |
2329
|
|
|
* This method can be used as static. |
2330
|
|
|
* |
2331
|
|
|
* @param int $lp_iv_id Learnpath Item View ID |
2332
|
|
|
* |
2333
|
|
|
* @return array |
2334
|
|
|
* |
2335
|
|
|
* @todo Transcode labels instead of switching to HTML (which requires to know the encoding of the LP) |
2336
|
|
|
*/ |
2337
|
|
|
public static function get_iv_interactions_array($lp_iv_id, $course_id = 0) |
2338
|
|
|
{ |
2339
|
|
|
$course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id; |
2340
|
|
|
$list = []; |
2341
|
|
|
$table = Database::get_course_table(TABLE_LP_IV_INTERACTION); |
2342
|
|
|
$lp_iv_id = (int) $lp_iv_id; |
2343
|
|
|
|
2344
|
|
|
if (empty($lp_iv_id) || empty($course_id)) { |
2345
|
|
|
return []; |
2346
|
|
|
} |
2347
|
|
|
|
2348
|
|
|
$sql = "SELECT * FROM $table |
2349
|
|
|
WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id |
2350
|
|
|
ORDER BY order_id ASC"; |
2351
|
|
|
$res = Database::query($sql); |
2352
|
|
|
$num = Database::num_rows($res); |
2353
|
|
|
if ($num > 0) { |
2354
|
|
|
$list[] = [ |
2355
|
|
|
'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES), |
2356
|
|
|
'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES), |
2357
|
|
|
'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES), |
2358
|
|
|
'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES), |
2359
|
|
|
'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES), |
2360
|
|
|
'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES), |
2361
|
|
|
'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES), |
2362
|
|
|
'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES), |
2363
|
|
|
'student_response_formatted' => '', |
2364
|
|
|
]; |
2365
|
|
|
while ($row = Database::fetch_array($res)) { |
2366
|
|
|
$studentResponseFormatted = urldecode($row['student_response']); |
2367
|
|
|
$content_student_response = explode('__|', $studentResponseFormatted); |
2368
|
|
|
if (count($content_student_response) > 0) { |
2369
|
|
|
if (count($content_student_response) >= 3) { |
2370
|
|
|
// Pop the element off the end of array. |
2371
|
|
|
array_pop($content_student_response); |
2372
|
|
|
} |
2373
|
|
|
$studentResponseFormatted = implode(',', $content_student_response); |
2374
|
|
|
} |
2375
|
|
|
|
2376
|
|
|
$list[] = [ |
2377
|
|
|
'order_id' => $row['order_id'] + 1, |
2378
|
|
|
'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that |
2379
|
|
|
'type' => $row['interaction_type'], |
2380
|
|
|
'time' => $row['completion_time'], |
2381
|
|
|
'correct_responses' => '', // Hide correct responses from students. |
2382
|
|
|
'student_response' => $row['student_response'], |
2383
|
|
|
'result' => $row['result'], |
2384
|
|
|
'latency' => $row['latency'], |
2385
|
|
|
'student_response_formatted' => $studentResponseFormatted, |
2386
|
|
|
]; |
2387
|
|
|
} |
2388
|
|
|
} |
2389
|
|
|
|
2390
|
|
|
return $list; |
2391
|
|
|
} |
2392
|
|
|
|
2393
|
|
|
/** |
2394
|
|
|
* Return the number of objectives for the given learnpath Item View ID. |
2395
|
|
|
* This method can be used as static. |
2396
|
|
|
* |
2397
|
|
|
* @param int $lp_iv_id Item View ID |
2398
|
|
|
* @param int $course_id Course ID |
2399
|
|
|
* |
2400
|
|
|
* @return int Number of objectives |
2401
|
|
|
*/ |
2402
|
|
|
public static function get_objectives_count_from_db($lp_iv_id, $course_id) |
2403
|
|
|
{ |
2404
|
|
|
$table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); |
2405
|
|
|
$course_id = (int) $course_id; |
2406
|
|
|
$lp_iv_id = (int) $lp_iv_id; |
2407
|
|
|
$sql = "SELECT count(*) FROM $table |
2408
|
|
|
WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id"; |
2409
|
|
|
//@todo seems that this always returns 0 |
2410
|
|
|
$res = Database::query($sql); |
2411
|
|
|
$num = 0; |
2412
|
|
|
if (Database::num_rows($res)) { |
2413
|
|
|
$row = Database::fetch_array($res); |
2414
|
|
|
$num = $row[0]; |
2415
|
|
|
} |
2416
|
|
|
|
2417
|
|
|
return $num; |
2418
|
|
|
} |
2419
|
|
|
|
2420
|
|
|
/** |
2421
|
|
|
* Return the objectives as an array for the given lp_iv_id. |
2422
|
|
|
* This method can be used as static. |
2423
|
|
|
* |
2424
|
|
|
* @param int $lpItemViewId Learnpath Item View ID |
2425
|
|
|
* @param int $course_id |
2426
|
|
|
* |
2427
|
|
|
* @return array |
2428
|
|
|
* |
2429
|
|
|
* @todo Translate labels |
2430
|
|
|
*/ |
2431
|
|
|
public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0) |
2432
|
|
|
{ |
2433
|
|
|
$course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id; |
2434
|
|
|
$lpItemViewId = (int) $lpItemViewId; |
2435
|
|
|
|
2436
|
|
|
if (empty($course_id) || empty($lpItemViewId)) { |
2437
|
|
|
return []; |
2438
|
|
|
} |
2439
|
|
|
|
2440
|
|
|
$table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); |
2441
|
|
|
$sql = "SELECT * FROM $table |
2442
|
|
|
WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId |
2443
|
|
|
ORDER BY order_id ASC"; |
2444
|
|
|
$res = Database::query($sql); |
2445
|
|
|
$num = Database::num_rows($res); |
2446
|
|
|
$list = []; |
2447
|
|
|
if ($num > 0) { |
2448
|
|
|
$list[] = [ |
2449
|
|
|
'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES), |
2450
|
|
|
'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES), |
2451
|
|
|
'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES), |
2452
|
|
|
'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES), |
2453
|
|
|
'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES), |
2454
|
|
|
'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES), |
2455
|
|
|
]; |
2456
|
|
|
while ($row = Database::fetch_array($res)) { |
2457
|
|
|
$list[] = [ |
2458
|
|
|
'order_id' => $row['order_id'] + 1, |
2459
|
|
|
'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F |
2460
|
|
|
'score_raw' => $row['score_raw'], |
2461
|
|
|
'score_max' => $row['score_max'], |
2462
|
|
|
'score_min' => $row['score_min'], |
2463
|
|
|
'status' => $row['status'], |
2464
|
|
|
]; |
2465
|
|
|
} |
2466
|
|
|
} |
2467
|
|
|
|
2468
|
|
|
return $list; |
2469
|
|
|
} |
2470
|
|
|
|
2471
|
|
|
/** |
2472
|
|
|
* Generate and return the table of contents for this learnpath. The (flat) table returned can be |
2473
|
|
|
* used by get_html_toc() to be ready to display. |
2474
|
|
|
*/ |
2475
|
|
|
public function get_toc(): array |
2476
|
|
|
{ |
2477
|
|
|
$toc = []; |
2478
|
|
|
foreach ($this->ordered_items as $item_id) { |
2479
|
|
|
// TODO: Change this link generation and use new function instead. |
2480
|
|
|
$toc[] = [ |
2481
|
|
|
'id' => $item_id, |
2482
|
|
|
'title' => $this->items[$item_id]->get_title(), |
2483
|
|
|
'status' => $this->items[$item_id]->get_status(false), |
2484
|
|
|
'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status(false)), |
2485
|
|
|
'level' => $this->items[$item_id]->get_level(), |
2486
|
|
|
'type' => $this->items[$item_id]->get_type(), |
2487
|
|
|
'description' => $this->items[$item_id]->get_description(), |
2488
|
|
|
'path' => $this->items[$item_id]->get_path(), |
2489
|
|
|
'parent' => $this->items[$item_id]->get_parent(), |
2490
|
|
|
]; |
2491
|
|
|
} |
2492
|
|
|
|
2493
|
|
|
return $toc; |
2494
|
|
|
} |
2495
|
|
|
|
2496
|
|
|
/** |
2497
|
|
|
* Returns the CSS class name associated with a given item status. |
2498
|
|
|
* |
2499
|
|
|
* @param $status string an item status |
2500
|
|
|
* |
2501
|
|
|
* @return string CSS class name |
2502
|
|
|
*/ |
2503
|
|
|
public static function getStatusCSSClassName($status) |
2504
|
|
|
{ |
2505
|
|
|
if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) { |
2506
|
|
|
return self::STATUS_CSS_CLASS_NAME[$status]; |
2507
|
|
|
} |
2508
|
|
|
|
2509
|
|
|
return ''; |
2510
|
|
|
} |
2511
|
|
|
|
2512
|
|
|
/** |
2513
|
|
|
* Generate and return the table of contents for this learnpath. The JS |
2514
|
|
|
* table returned is used inside of scorm_api.php. |
2515
|
|
|
* |
2516
|
|
|
* @param string $varname |
2517
|
|
|
* |
2518
|
|
|
* @return string A JS array variable construction |
2519
|
|
|
*/ |
2520
|
|
|
public function get_items_details_as_js($varname = 'olms.lms_item_types') |
2521
|
|
|
{ |
2522
|
|
|
$toc = $varname.' = new Array();'; |
2523
|
|
|
foreach ($this->ordered_items as $item_id) { |
2524
|
|
|
$toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';"; |
2525
|
|
|
} |
2526
|
|
|
|
2527
|
|
|
return $toc; |
2528
|
|
|
} |
2529
|
|
|
|
2530
|
|
|
/** |
2531
|
|
|
* Gets the learning path type. |
2532
|
|
|
* |
2533
|
|
|
* @param bool $get_name Return the name? If false, return the ID. Default is false. |
2534
|
|
|
* |
2535
|
|
|
* @return mixed Type ID or name, depending on the parameter |
2536
|
|
|
*/ |
2537
|
|
|
public function get_type($get_name = false) |
2538
|
|
|
{ |
2539
|
|
|
$res = false; |
2540
|
|
|
if (!empty($this->type) && (!$get_name)) { |
2541
|
|
|
$res = $this->type; |
2542
|
|
|
} |
2543
|
|
|
|
2544
|
|
|
return $res; |
2545
|
|
|
} |
2546
|
|
|
|
2547
|
|
|
/** |
2548
|
|
|
* Gets the learning path type as static method. |
2549
|
|
|
* |
2550
|
|
|
* @param int $lp_id |
2551
|
|
|
* |
2552
|
|
|
* @return mixed Type ID or name, depending on the parameter |
2553
|
|
|
*/ |
2554
|
|
|
public static function get_type_static($lp_id = 0) |
2555
|
|
|
{ |
2556
|
|
|
$tbl_lp = Database::get_course_table(TABLE_LP_MAIN); |
2557
|
|
|
$lp_id = (int) $lp_id; |
2558
|
|
|
$sql = "SELECT lp_type FROM $tbl_lp |
2559
|
|
|
WHERE iid = $lp_id"; |
2560
|
|
|
$res = Database::query($sql); |
2561
|
|
|
if (false === $res) { |
2562
|
|
|
return null; |
2563
|
|
|
} |
2564
|
|
|
if (Database::num_rows($res) <= 0) { |
2565
|
|
|
return null; |
2566
|
|
|
} |
2567
|
|
|
$row = Database::fetch_array($res); |
2568
|
|
|
|
2569
|
|
|
return $row['lp_type']; |
2570
|
|
|
} |
2571
|
|
|
|
2572
|
|
|
/** |
2573
|
|
|
* Gets a flat list of item IDs ordered for display (level by level ordered by order_display) |
2574
|
|
|
* This method can be used as abstract and is recursive. |
2575
|
|
|
* |
2576
|
|
|
* @param CLp $lp |
2577
|
|
|
* @param int $parent Parent ID of the items to look for |
2578
|
|
|
* |
2579
|
|
|
* @return array Ordered list of item IDs (empty array on error) |
2580
|
|
|
*/ |
2581
|
|
|
public static function get_flat_ordered_items_list(CLp $lp, $parent = 0) |
2582
|
|
|
{ |
2583
|
|
|
$parent = (int) $parent; |
2584
|
|
|
$lpItemRepo = Container::getLpItemRepository(); |
2585
|
|
|
if (empty($parent)) { |
2586
|
|
|
$rootItem = $lpItemRepo->getRootItem($lp->getIid()); |
2587
|
|
|
if (null !== $rootItem) { |
2588
|
|
|
$parent = $rootItem->getIid(); |
2589
|
|
|
} |
2590
|
|
|
} |
2591
|
|
|
|
2592
|
|
|
if (empty($parent)) { |
2593
|
|
|
return []; |
2594
|
|
|
} |
2595
|
|
|
|
2596
|
|
|
$criteria = new Criteria(); |
2597
|
|
|
$criteria |
2598
|
|
|
->where($criteria->expr()->neq('path', 'root')) |
2599
|
|
|
->orderBy( |
2600
|
|
|
[ |
2601
|
|
|
'displayOrder' => Criteria::ASC, |
2602
|
|
|
] |
2603
|
|
|
); |
2604
|
|
|
$items = $lp->getItems()->matching($criteria); |
2605
|
|
|
$items = $items->filter( |
2606
|
|
|
function (CLpItem $element) use ($parent) { |
2607
|
|
|
if ('root' === $element->getPath()) { |
2608
|
|
|
return false; |
2609
|
|
|
} |
2610
|
|
|
|
2611
|
|
|
if (null !== $element->getParent()) { |
2612
|
|
|
return $element->getParent()->getIid() === $parent; |
2613
|
|
|
} |
2614
|
|
|
return false; |
2615
|
|
|
|
2616
|
|
|
} |
2617
|
|
|
); |
2618
|
|
|
$list = []; |
2619
|
|
|
foreach ($items as $item) { |
2620
|
|
|
$itemId = $item->getIid(); |
2621
|
|
|
$sublist = self::get_flat_ordered_items_list($lp, $itemId); |
2622
|
|
|
$list[] = $itemId; |
2623
|
|
|
foreach ($sublist as $subItem) { |
2624
|
|
|
$list[] = $subItem; |
2625
|
|
|
} |
2626
|
|
|
} |
2627
|
|
|
|
2628
|
|
|
return $list; |
2629
|
|
|
} |
2630
|
|
|
|
2631
|
|
|
public static function getChapterTypes(): array |
2632
|
|
|
{ |
2633
|
|
|
return [ |
2634
|
|
|
'dir', |
2635
|
|
|
]; |
2636
|
|
|
} |
2637
|
|
|
|
2638
|
|
|
/** |
2639
|
|
|
* Uses the table generated by get_toc() and returns an HTML-formatted string ready to display. |
2640
|
|
|
* |
2641
|
|
|
* @return array HTML TOC ready to display |
2642
|
|
|
*/ |
2643
|
|
|
public function getListArrayToc() |
2644
|
|
|
{ |
2645
|
|
|
$lpItemRepo = Container::getLpItemRepository(); |
2646
|
|
|
$itemRoot = $lpItemRepo->getRootItem($this->get_id()); |
2647
|
|
|
$options = [ |
2648
|
|
|
'decorate' => false, |
2649
|
|
|
]; |
2650
|
|
|
|
2651
|
|
|
return $lpItemRepo->childrenHierarchy($itemRoot, false, $options); |
2652
|
|
|
} |
2653
|
|
|
|
2654
|
|
|
/** |
2655
|
|
|
* Returns an HTML-formatted string ready to display with teacher buttons |
2656
|
|
|
* in LP view menu. |
2657
|
|
|
* |
2658
|
|
|
* @return string HTML TOC ready to display |
2659
|
|
|
*/ |
2660
|
|
|
public function get_teacher_toc_buttons() |
2661
|
|
|
{ |
2662
|
|
|
$isAllow = api_is_allowed_to_edit(null, true, false, false); |
2663
|
|
|
$hideIcons = api_get_configuration_value('hide_teacher_icons_lp'); |
2664
|
|
|
$html = ''; |
2665
|
|
|
if ($isAllow && false == $hideIcons) { |
2666
|
|
|
if ($this->get_lp_session_id() == api_get_session_id()) { |
2667
|
|
|
$html .= '<div id="actions_lp" class="actions_lp"><hr>'; |
2668
|
|
|
$html .= '<div class="flex flex-wrap gap-1 justify-center">'; |
2669
|
|
|
$html .= "<a |
2670
|
|
|
class='btn btn-sm btn--plain' |
2671
|
|
|
href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' |
2672
|
|
|
target='_parent'>". |
2673
|
|
|
Display::getMdiIcon('pencil').get_lang('Edit')."</a>"; |
2674
|
|
|
$html .= '<a |
2675
|
|
|
class="btn btn-sm btn--plain" |
2676
|
|
|
href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'. |
2677
|
|
|
Display::getMdiIcon('hammer-wrench').get_lang('Settings').'</a>'; |
2678
|
|
|
$html .= '</div>'; |
2679
|
|
|
$html .= '</div>'; |
2680
|
|
|
} |
2681
|
|
|
} |
2682
|
|
|
|
2683
|
|
|
return $html; |
2684
|
|
|
} |
2685
|
|
|
|
2686
|
|
|
/** |
2687
|
|
|
* Gets the learnpath name/title. |
2688
|
|
|
* |
2689
|
|
|
* @return string Learnpath name/title |
2690
|
|
|
*/ |
2691
|
|
|
public function get_name() |
2692
|
|
|
{ |
2693
|
|
|
if (!empty($this->name)) { |
2694
|
|
|
return $this->name; |
2695
|
|
|
} |
2696
|
|
|
|
2697
|
|
|
return 'N/A'; |
2698
|
|
|
} |
2699
|
|
|
|
2700
|
|
|
/** |
2701
|
|
|
* @return string |
2702
|
|
|
*/ |
2703
|
|
|
public function getNameNoTags() |
2704
|
|
|
{ |
2705
|
|
|
return strip_tags($this->get_name()); |
2706
|
|
|
} |
2707
|
|
|
|
2708
|
|
|
/** |
2709
|
|
|
* Gets a link to the resource from the present location, depending on item ID. |
2710
|
|
|
* |
2711
|
|
|
* @param string $type Type of link expected |
2712
|
|
|
* @param int $item_id Learnpath item ID |
2713
|
|
|
* @param bool $provided_toc |
2714
|
|
|
* |
2715
|
|
|
* @return string $provided_toc Link to the lp_item resource |
2716
|
|
|
*/ |
2717
|
|
|
public function get_link($type = 'http', $item_id = 0, $provided_toc = false) |
2718
|
|
|
{ |
2719
|
|
|
$course_id = $this->get_course_int_id(); |
2720
|
|
|
$item_id = (int) $item_id; |
2721
|
|
|
|
2722
|
|
|
if (empty($item_id)) { |
2723
|
|
|
$item_id = $this->get_current_item_id(); |
2724
|
|
|
|
2725
|
|
|
if (empty($item_id)) { |
2726
|
|
|
//still empty, this means there was no item_id given and we are not in an object context or |
2727
|
|
|
//the object property is empty, return empty link |
2728
|
|
|
$this->first(); |
2729
|
|
|
|
2730
|
|
|
return ''; |
2731
|
|
|
} |
2732
|
|
|
} |
2733
|
|
|
|
2734
|
|
|
$file = ''; |
2735
|
|
|
$lp_table = Database::get_course_table(TABLE_LP_MAIN); |
2736
|
|
|
$lp_item_table = Database::get_course_table(TABLE_LP_ITEM); |
2737
|
|
|
$lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); |
2738
|
|
|
|
2739
|
|
|
$sql = "SELECT |
2740
|
|
|
l.lp_type as ltype, |
2741
|
|
|
l.path as lpath, |
2742
|
|
|
li.item_type as litype, |
2743
|
|
|
li.path as lipath, |
2744
|
|
|
li.parameters as liparams |
2745
|
|
|
FROM $lp_table l |
2746
|
|
|
INNER JOIN $lp_item_table li |
2747
|
|
|
ON (li.lp_id = l.iid) |
2748
|
|
|
WHERE |
2749
|
|
|
li.iid = $item_id |
2750
|
|
|
"; |
2751
|
|
|
$res = Database::query($sql); |
2752
|
|
|
if (Database::num_rows($res) > 0) { |
2753
|
|
|
$row = Database::fetch_array($res); |
2754
|
|
|
$lp_type = $row['ltype']; |
2755
|
|
|
$lp_path = $row['lpath']; |
2756
|
|
|
$lp_item_type = $row['litype']; |
2757
|
|
|
$lp_item_path = $row['lipath']; |
2758
|
|
|
$lp_item_params = $row['liparams']; |
2759
|
|
|
if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) { |
2760
|
|
|
[$lp_item_path, $lp_item_params] = explode('?', $lp_item_path); |
2761
|
|
|
} |
2762
|
|
|
//$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path(); |
2763
|
|
|
if ('http' === $type) { |
2764
|
|
|
//web path |
2765
|
|
|
//$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path(); |
2766
|
|
|
} else { |
2767
|
|
|
//$course_path = $sys_course_path; //system path |
2768
|
|
|
} |
2769
|
|
|
|
2770
|
|
|
// Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc), |
2771
|
|
|
// then change the lp type to thread it as a normal Chamilo LP not a SCO. |
2772
|
|
|
if (in_array( |
2773
|
|
|
$lp_item_type, |
2774
|
|
|
['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication', 'survey'] |
2775
|
|
|
) |
2776
|
|
|
) { |
2777
|
|
|
$lp_type = CLp::LP_TYPE; |
2778
|
|
|
} |
2779
|
|
|
|
2780
|
|
|
// Now go through the specific cases to get the end of the path |
2781
|
|
|
// @todo Use constants instead of int values. |
2782
|
|
|
switch ($lp_type) { |
2783
|
|
|
case CLp::LP_TYPE: |
2784
|
|
|
$file = self::rl_get_resource_link_for_learnpath( |
2785
|
|
|
$course_id, |
2786
|
|
|
$this->get_id(), |
2787
|
|
|
$item_id, |
2788
|
|
|
$this->get_view_id() |
2789
|
|
|
); |
2790
|
|
|
switch ($lp_item_type) { |
2791
|
|
|
case 'document': |
2792
|
|
|
// Shows a button to download the file instead of just downloading the file directly. |
2793
|
|
|
$documentPathInfo = pathinfo($file); |
2794
|
|
|
if (isset($documentPathInfo['extension'])) { |
2795
|
|
|
$parsed = parse_url($documentPathInfo['extension']); |
2796
|
|
|
if (isset($parsed['path'])) { |
2797
|
|
|
$extension = $parsed['path']; |
2798
|
|
|
$extensionsToDownload = [ |
2799
|
|
|
'zip', |
2800
|
|
|
'ppt', |
2801
|
|
|
'pptx', |
2802
|
|
|
'ods', |
2803
|
|
|
'xlsx', |
2804
|
|
|
'xls', |
2805
|
|
|
'csv', |
2806
|
|
|
'doc', |
2807
|
|
|
'docx', |
2808
|
|
|
'dot', |
2809
|
|
|
]; |
2810
|
|
|
|
2811
|
|
|
if (in_array($extension, $extensionsToDownload)) { |
2812
|
|
|
$file = api_get_path(WEB_CODE_PATH). |
2813
|
|
|
'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq(); |
2814
|
|
|
} |
2815
|
|
|
} |
2816
|
|
|
} |
2817
|
|
|
break; |
2818
|
|
|
case 'dir': |
2819
|
|
|
$file = 'lp_content.php?type=dir'; |
2820
|
|
|
break; |
2821
|
|
|
case 'link': |
2822
|
|
|
if (Link::is_youtube_link($file)) { |
2823
|
|
|
$src = Link::get_youtube_video_id($file); |
2824
|
|
|
$file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src; |
2825
|
|
|
} elseif (Link::isVimeoLink($file)) { |
2826
|
|
|
$src = Link::getVimeoLinkId($file); |
2827
|
|
|
$file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src; |
2828
|
|
|
} else { |
2829
|
|
|
// If the current site is HTTPS and the link is |
2830
|
|
|
// HTTP, browsers will refuse opening the link |
2831
|
|
|
$urlId = api_get_current_access_url_id(); |
2832
|
|
|
$url = api_get_access_url($urlId, false); |
2833
|
|
|
$protocol = substr($url['url'], 0, 5); |
2834
|
|
|
if ('https' === $protocol) { |
2835
|
|
|
$linkProtocol = substr($file, 0, 5); |
2836
|
|
|
if ('http:' === $linkProtocol) { |
2837
|
|
|
//this is the special intervention case |
2838
|
|
|
$file = api_get_path(WEB_CODE_PATH). |
2839
|
|
|
'lp/embed.php?type=nonhttps&source='.urlencode($file); |
2840
|
|
|
} |
2841
|
|
|
} |
2842
|
|
|
} |
2843
|
|
|
break; |
2844
|
|
|
case 'quiz': |
2845
|
|
|
// Check how much attempts of a exercise exits in lp |
2846
|
|
|
$lp_item_id = $this->get_current_item_id(); |
2847
|
|
|
$lp_view_id = $this->get_view_id(); |
2848
|
|
|
|
2849
|
|
|
$prevent_reinit = null; |
2850
|
|
|
if (isset($this->items[$this->current])) { |
2851
|
|
|
$prevent_reinit = $this->items[$this->current]->get_prevent_reinit(); |
2852
|
|
|
} |
2853
|
|
|
|
2854
|
|
|
if (empty($provided_toc)) { |
2855
|
|
|
$list = $this->get_toc(); |
2856
|
|
|
} else { |
2857
|
|
|
$list = $provided_toc; |
2858
|
|
|
} |
2859
|
|
|
|
2860
|
|
|
$type_quiz = false; |
2861
|
|
|
foreach ($list as $toc) { |
2862
|
|
|
if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) { |
2863
|
|
|
$type_quiz = true; |
2864
|
|
|
} |
2865
|
|
|
} |
2866
|
|
|
|
2867
|
|
|
if ($type_quiz) { |
2868
|
|
|
$lp_item_id = (int) $lp_item_id; |
2869
|
|
|
$lp_view_id = (int) $lp_view_id; |
2870
|
|
|
$sql = "SELECT count(*) FROM $lp_item_view_table |
2871
|
|
|
WHERE |
2872
|
|
|
lp_item_id='".$lp_item_id."' AND |
2873
|
|
|
lp_view_id ='".$lp_view_id."' AND |
2874
|
|
|
status='completed'"; |
2875
|
|
|
$result = Database::query($sql); |
2876
|
|
|
$row_count = Database:: fetch_row($result); |
2877
|
|
|
$count_item_view = (int) $row_count[0]; |
2878
|
|
|
$not_multiple_attempt = 0; |
2879
|
|
|
if (1 === $prevent_reinit && $count_item_view > 0) { |
2880
|
|
|
$not_multiple_attempt = 1; |
2881
|
|
|
} |
2882
|
|
|
$file .= '¬_multiple_attempt='.$not_multiple_attempt; |
2883
|
|
|
} |
2884
|
|
|
break; |
2885
|
|
|
} |
2886
|
|
|
|
2887
|
|
|
$tmp_array = explode('/', $file); |
2888
|
|
|
$document_name = $tmp_array[count($tmp_array) - 1]; |
2889
|
|
|
if (strpos($document_name, '_DELETED_')) { |
2890
|
|
|
$file = 'blank.php?error=document_deleted'; |
2891
|
|
|
} |
2892
|
|
|
break; |
2893
|
|
|
case CLp::SCORM_TYPE: |
2894
|
|
|
if ('dir' !== $lp_item_type) { |
2895
|
|
|
// Quite complex here: |
2896
|
|
|
// We want to make sure 'http://' (and similar) links can |
2897
|
|
|
// be loaded as is (withouth the Chamilo path in front) but |
2898
|
|
|
// some contents use this form: resource.htm?resource=http://blablabla |
2899
|
|
|
// which means we have to find a protocol at the path's start, otherwise |
2900
|
|
|
// it should not be considered as an external URL. |
2901
|
|
|
// if ($this->prerequisites_match($item_id)) { |
2902
|
|
|
if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) { |
2903
|
|
|
if ($this->debug > 2) { |
2904
|
|
|
error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0); |
2905
|
|
|
} |
2906
|
|
|
// Distant url, return as is. |
2907
|
|
|
$file = $lp_item_path; |
2908
|
|
|
} else { |
2909
|
|
|
if ($this->debug > 2) { |
2910
|
|
|
error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path); |
2911
|
|
|
} |
2912
|
|
|
// Prevent getting untranslatable urls. |
2913
|
|
|
$lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); |
2914
|
|
|
$lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); |
2915
|
|
|
|
2916
|
|
|
/*$asset = $this->getEntity()->getAsset(); |
2917
|
|
|
$folder = Container::getAssetRepository()->getFolder($asset); |
2918
|
|
|
$hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path); |
2919
|
|
|
$file = null; |
2920
|
|
|
if ($hasFile) { |
2921
|
|
|
$file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path; |
2922
|
|
|
}*/ |
2923
|
|
|
$file = $this->scormUrl.$lp_item_path; |
2924
|
|
|
|
2925
|
|
|
// Prepare the path. |
2926
|
|
|
/*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path; |
2927
|
|
|
// TODO: Fix this for urls with protocol header. |
2928
|
|
|
$file = str_replace('//', '/', $file); |
2929
|
|
|
$file = str_replace(':/', '://', $file); |
2930
|
|
|
if ('/' === substr($lp_path, -1)) { |
2931
|
|
|
$lp_path = substr($lp_path, 0, -1); |
2932
|
|
|
}*/ |
2933
|
|
|
/*if (!$hasFile) { |
2934
|
|
|
// if file not found. |
2935
|
|
|
$decoded = html_entity_decode($lp_item_path); |
2936
|
|
|
[$decoded] = explode('?', $decoded); |
2937
|
|
|
if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) { |
2938
|
|
|
$file = self::rl_get_resource_link_for_learnpath( |
2939
|
|
|
$course_id, |
2940
|
|
|
$this->get_id(), |
2941
|
|
|
$item_id, |
2942
|
|
|
$this->get_view_id() |
2943
|
|
|
); |
2944
|
|
|
if (empty($file)) { |
2945
|
|
|
$file = 'blank.php?error=document_not_found'; |
2946
|
|
|
} else { |
2947
|
|
|
$tmp_array = explode('/', $file); |
2948
|
|
|
$document_name = $tmp_array[count($tmp_array) - 1]; |
2949
|
|
|
if (strpos($document_name, '_DELETED_')) { |
2950
|
|
|
$file = 'blank.php?error=document_deleted'; |
2951
|
|
|
} else { |
2952
|
|
|
$file = 'blank.php?error=document_not_found'; |
2953
|
|
|
} |
2954
|
|
|
} |
2955
|
|
|
} else { |
2956
|
|
|
$file = $course_path.'/scorm/'.$lp_path.'/'.$decoded; |
2957
|
|
|
} |
2958
|
|
|
}*/ |
2959
|
|
|
} |
2960
|
|
|
|
2961
|
|
|
// We want to use parameters if they were defined in the imsmanifest |
2962
|
|
|
if (false === strpos($file, 'blank.php')) { |
2963
|
|
|
$lp_item_params = ltrim($lp_item_params, '?'); |
2964
|
|
|
$file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params; |
2965
|
|
|
} |
2966
|
|
|
} else { |
2967
|
|
|
$file = 'lp_content.php?type=dir'; |
2968
|
|
|
} |
2969
|
|
|
break; |
2970
|
|
|
case CLp::AICC_TYPE: |
2971
|
|
|
// Formatting AICC HACP append URL. |
2972
|
|
|
$aicc_append = '?aicc_sid='. |
2973
|
|
|
urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&'; |
2974
|
|
|
if (!empty($lp_item_params)) { |
2975
|
|
|
$aicc_append .= $lp_item_params.'&'; |
2976
|
|
|
} |
2977
|
|
|
if ('dir' !== $lp_item_type) { |
2978
|
|
|
// Quite complex here: |
2979
|
|
|
// We want to make sure 'http://' (and similar) links can |
2980
|
|
|
// be loaded as is (withouth the Chamilo path in front) but |
2981
|
|
|
// some contents use this form: resource.htm?resource=http://blablabla |
2982
|
|
|
// which means we have to find a protocol at the path's start, otherwise |
2983
|
|
|
// it should not be considered as an external URL. |
2984
|
|
|
if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) { |
2985
|
|
|
if ($this->debug > 2) { |
2986
|
|
|
error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0); |
2987
|
|
|
} |
2988
|
|
|
// Distant url, return as is. |
2989
|
|
|
$file = $lp_item_path; |
2990
|
|
|
// Enabled and modified by Ivan Tcholakov, 16-OCT-2008. |
2991
|
|
|
/* |
2992
|
|
|
if (stristr($file,'<servername>') !== false) { |
2993
|
|
|
$file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path); |
2994
|
|
|
} |
2995
|
|
|
*/ |
2996
|
|
|
if (false !== stripos($file, '<servername>')) { |
2997
|
|
|
//$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path); |
2998
|
|
|
$web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path)); |
2999
|
|
|
$file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path); |
3000
|
|
|
} |
3001
|
|
|
|
3002
|
|
|
$file .= $aicc_append; |
3003
|
|
|
} else { |
3004
|
|
|
if ($this->debug > 2) { |
3005
|
|
|
error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0); |
3006
|
|
|
} |
3007
|
|
|
// Prevent getting untranslatable urls. |
3008
|
|
|
$lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); |
3009
|
|
|
$lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); |
3010
|
|
|
// Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name. |
3011
|
|
|
$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path; |
3012
|
|
|
// TODO: Fix this for urls with protocol header. |
3013
|
|
|
$file = str_replace('//', '/', $file); |
3014
|
|
|
$file = str_replace(':/', '://', $file); |
3015
|
|
|
$file .= $aicc_append; |
3016
|
|
|
} |
3017
|
|
|
} else { |
3018
|
|
|
$file = 'lp_content.php?type=dir'; |
3019
|
|
|
} |
3020
|
|
|
break; |
3021
|
|
|
case 4: |
3022
|
|
|
default: |
3023
|
|
|
break; |
3024
|
|
|
} |
3025
|
|
|
// Replace & by & because & will break URL with params |
3026
|
|
|
$file = !empty($file) ? str_replace('&', '&', $file) : ''; |
3027
|
|
|
} |
3028
|
|
|
if ($this->debug > 2) { |
3029
|
|
|
error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0); |
3030
|
|
|
} |
3031
|
|
|
|
3032
|
|
|
return $file; |
3033
|
|
|
} |
3034
|
|
|
|
3035
|
|
|
/** |
3036
|
|
|
* Gets the latest usable view or generate a new one. |
3037
|
|
|
* |
3038
|
|
|
* @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table |
3039
|
|
|
* @param int $userId The user ID, as $this->get_user_id() is not always available |
3040
|
|
|
* |
3041
|
|
|
* @return int DB lp_view id |
3042
|
|
|
*/ |
3043
|
|
|
public function get_view($attempt_num = 0, $userId = null) |
3044
|
|
|
{ |
3045
|
|
|
$search = ''; |
3046
|
|
|
$attempt_num = (int) $attempt_num; |
3047
|
|
|
// Use $attempt_num to enable multi-views management (disabled so far). |
3048
|
|
|
if (!empty($attempt_num)) { |
3049
|
|
|
$search = 'AND view_count = '.$attempt_num; |
3050
|
|
|
} |
3051
|
|
|
|
3052
|
|
|
$course_id = api_get_course_int_id(); |
3053
|
|
|
$sessionId = api_get_session_id(); |
3054
|
|
|
|
3055
|
|
|
// Check user ID. |
3056
|
|
|
if (empty($userId)) { |
3057
|
|
|
if (empty($this->get_user_id())) { |
3058
|
|
|
$this->error = 'User ID is empty in learnpath::get_view()'; |
3059
|
|
|
|
3060
|
|
|
return null; |
3061
|
|
|
} else { |
3062
|
|
|
$userId = $this->get_user_id(); |
3063
|
|
|
} |
3064
|
|
|
} |
3065
|
|
|
$sessionCondition = api_get_session_condition($sessionId); |
3066
|
|
|
|
3067
|
|
|
// When missing $attempt_num, search for a unique lp_view record for this lp and user. |
3068
|
|
|
$table = Database::get_course_table(TABLE_LP_VIEW); |
3069
|
|
|
$sql = "SELECT iid FROM $table |
3070
|
|
|
WHERE |
3071
|
|
|
c_id = $course_id AND |
3072
|
|
|
lp_id = ".$this->get_id()." AND |
3073
|
|
|
user_id = ".$userId." |
3074
|
|
|
$sessionCondition |
3075
|
|
|
$search |
3076
|
|
|
ORDER BY view_count DESC"; |
3077
|
|
|
$res = Database::query($sql); |
3078
|
|
|
if (Database::num_rows($res) > 0) { |
3079
|
|
|
$row = Database::fetch_array($res); |
3080
|
|
|
$this->lp_view_id = $row['iid']; |
3081
|
|
|
} elseif (!api_is_invitee()) { |
3082
|
|
|
$params = [ |
3083
|
|
|
'c_id' => $course_id, |
3084
|
|
|
'lp_id' => $this->get_id(), |
3085
|
|
|
'user_id' => $this->get_user_id(), |
3086
|
|
|
'view_count' => 1, |
3087
|
|
|
'last_item' => 0, |
3088
|
|
|
]; |
3089
|
|
|
if (!empty($sessionId)) { |
3090
|
|
|
$params['session_id'] = $sessionId; |
3091
|
|
|
} |
3092
|
|
|
$this->lp_view_id = Database::insert($table, $params); |
3093
|
|
|
} |
3094
|
|
|
|
3095
|
|
|
return $this->lp_view_id; |
3096
|
|
|
} |
3097
|
|
|
|
3098
|
|
|
/** |
3099
|
|
|
* Gets the current view id. |
3100
|
|
|
* |
3101
|
|
|
* @return int View ID (from lp_view) |
3102
|
|
|
*/ |
3103
|
|
|
public function get_view_id() |
3104
|
|
|
{ |
3105
|
|
|
if (!empty($this->lp_view_id)) { |
3106
|
|
|
return (int) $this->lp_view_id; |
3107
|
|
|
} |
3108
|
|
|
|
3109
|
|
|
return 0; |
3110
|
|
|
} |
3111
|
|
|
|
3112
|
|
|
/** |
3113
|
|
|
* Gets the update queue. |
3114
|
|
|
* |
3115
|
|
|
* @return array Array containing IDs of items to be updated by JavaScript |
3116
|
|
|
*/ |
3117
|
|
|
public function get_update_queue() |
3118
|
|
|
{ |
3119
|
|
|
return $this->update_queue; |
3120
|
|
|
} |
3121
|
|
|
|
3122
|
|
|
/** |
3123
|
|
|
* Gets the user ID. |
3124
|
|
|
* |
3125
|
|
|
* @return int User ID |
3126
|
|
|
*/ |
3127
|
|
|
public function get_user_id() |
3128
|
|
|
{ |
3129
|
|
|
if (!empty($this->user_id)) { |
3130
|
|
|
return (int) $this->user_id; |
3131
|
|
|
} |
3132
|
|
|
|
3133
|
|
|
return false; |
3134
|
|
|
} |
3135
|
|
|
|
3136
|
|
|
/** |
3137
|
|
|
* Checks if any of the items has an audio element attached. |
3138
|
|
|
* |
3139
|
|
|
* @return bool True or false |
3140
|
|
|
*/ |
3141
|
|
|
public function has_audio() |
3142
|
|
|
{ |
3143
|
|
|
$has = false; |
3144
|
|
|
foreach ($this->items as $i => $item) { |
3145
|
|
|
if (!empty($this->items[$i]->audio)) { |
3146
|
|
|
$has = true; |
3147
|
|
|
break; |
3148
|
|
|
} |
3149
|
|
|
} |
3150
|
|
|
|
3151
|
|
|
return $has; |
3152
|
|
|
} |
3153
|
|
|
|
3154
|
|
|
/** |
3155
|
|
|
* Updates learnpath attributes to point to the next element |
3156
|
|
|
* The last part is similar to set_current_item but processing the other way around. |
3157
|
|
|
*/ |
3158
|
|
|
public function next() |
3159
|
|
|
{ |
3160
|
|
|
if ($this->debug > 0) { |
3161
|
|
|
error_log('In learnpath::next()', 0); |
3162
|
|
|
} |
3163
|
|
|
$this->last = $this->get_current_item_id(); |
3164
|
|
|
$this->items[$this->last]->save( |
3165
|
|
|
false, |
3166
|
|
|
$this->prerequisites_match($this->last) |
3167
|
|
|
); |
3168
|
|
|
$this->autocomplete_parents($this->last); |
3169
|
|
|
$new_index = $this->get_next_index(); |
3170
|
|
|
if ($this->debug > 2) { |
3171
|
|
|
error_log('New index: '.$new_index, 0); |
3172
|
|
|
} |
3173
|
|
|
$this->index = $new_index; |
3174
|
|
|
if ($this->debug > 2) { |
3175
|
|
|
error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0); |
3176
|
|
|
} |
3177
|
|
|
$this->current = $this->ordered_items[$new_index]; |
3178
|
|
|
if ($this->debug > 2) { |
3179
|
|
|
error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0); |
3180
|
|
|
} |
3181
|
|
|
} |
3182
|
|
|
|
3183
|
|
|
/** |
3184
|
|
|
* Open a resource = initialise all local variables relative to this resource. Depending on the child |
3185
|
|
|
* class, this might be redefined to allow several behaviours depending on the document type. |
3186
|
|
|
* |
3187
|
|
|
* @param int $id Resource ID |
3188
|
|
|
*/ |
3189
|
|
|
public function open($id) |
3190
|
|
|
{ |
3191
|
|
|
// TODO: |
3192
|
|
|
// set the current resource attribute to this resource |
3193
|
|
|
// switch on element type (redefine in child class?) |
3194
|
|
|
// set status for this item to "opened" |
3195
|
|
|
// start timer |
3196
|
|
|
// initialise score |
3197
|
|
|
$this->index = 0; //or = the last item seen (see $this->last) |
3198
|
|
|
} |
3199
|
|
|
|
3200
|
|
|
/** |
3201
|
|
|
* Check that all prerequisites are fulfilled. Returns true and an |
3202
|
|
|
* empty string on success, returns false |
3203
|
|
|
* and the prerequisite string on error. |
3204
|
|
|
* This function is based on the rules for aicc_script language as |
3205
|
|
|
* described in the SCORM 1.2 CAM documentation page 108. |
3206
|
|
|
* |
3207
|
|
|
* @param int $itemId Optional item ID. If none given, uses the current open item. |
3208
|
|
|
* |
3209
|
|
|
* @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites |
3210
|
|
|
* string otherwise |
3211
|
|
|
*/ |
3212
|
|
|
public function prerequisites_match($itemId = null) |
3213
|
|
|
{ |
3214
|
|
|
$allow = ('true' === api_get_setting('lp.allow_teachers_to_access_blocked_lp_by_prerequisite')); |
3215
|
|
|
if ($allow) { |
3216
|
|
|
if (api_is_allowed_to_edit() || |
3217
|
|
|
api_is_platform_admin(true) || |
3218
|
|
|
api_is_drh() || |
3219
|
|
|
api_is_coach(api_get_session_id(), api_get_course_int_id()) |
3220
|
|
|
) { |
3221
|
|
|
return true; |
3222
|
|
|
} |
3223
|
|
|
} |
3224
|
|
|
|
3225
|
|
|
$debug = $this->debug; |
3226
|
|
|
if ($debug > 0) { |
3227
|
|
|
error_log('In learnpath::prerequisites_match()'); |
3228
|
|
|
} |
3229
|
|
|
|
3230
|
|
|
if (empty($itemId)) { |
3231
|
|
|
$itemId = $this->current; |
3232
|
|
|
} |
3233
|
|
|
|
3234
|
|
|
$currentItem = $this->getItem($itemId); |
3235
|
|
|
|
3236
|
|
|
if ($currentItem) { |
3237
|
|
|
if (2 == $this->type) { |
3238
|
|
|
// Getting prereq from scorm |
3239
|
|
|
$prereq_string = $this->get_scorm_prereq_string($itemId); |
3240
|
|
|
} else { |
3241
|
|
|
$prereq_string = $currentItem->get_prereq_string(); |
3242
|
|
|
} |
3243
|
|
|
|
3244
|
|
|
if (empty($prereq_string)) { |
3245
|
|
|
if ($debug > 0) { |
3246
|
|
|
error_log('Found prereq_string is empty return true'); |
3247
|
|
|
} |
3248
|
|
|
|
3249
|
|
|
return true; |
3250
|
|
|
} |
3251
|
|
|
|
3252
|
|
|
// Clean spaces. |
3253
|
|
|
$prereq_string = str_replace(' ', '', $prereq_string); |
3254
|
|
|
if ($debug > 0) { |
3255
|
|
|
error_log('Found prereq_string: '.$prereq_string, 0); |
3256
|
|
|
} |
3257
|
|
|
|
3258
|
|
|
// Now send to the parse_prereq() function that will check this component's prerequisites. |
3259
|
|
|
$result = $currentItem->parse_prereq( |
3260
|
|
|
$prereq_string, |
3261
|
|
|
$this->items, |
3262
|
|
|
$this->refs_list, |
3263
|
|
|
$this->get_user_id() |
3264
|
|
|
); |
3265
|
|
|
|
3266
|
|
|
if (false === $result) { |
3267
|
|
|
$this->set_error_msg($currentItem->prereq_alert); |
3268
|
|
|
} |
3269
|
|
|
} else { |
3270
|
|
|
$result = true; |
3271
|
|
|
if ($debug > 1) { |
3272
|
|
|
error_log('$this->items['.$itemId.'] was not an object', 0); |
3273
|
|
|
} |
3274
|
|
|
} |
3275
|
|
|
|
3276
|
|
|
if ($debug > 1) { |
3277
|
|
|
error_log('End of prerequisites_match(). Error message is now '.$this->error, 0); |
3278
|
|
|
} |
3279
|
|
|
|
3280
|
|
|
return $result; |
3281
|
|
|
} |
3282
|
|
|
|
3283
|
|
|
/** |
3284
|
|
|
* Updates learnpath attributes to point to the previous element |
3285
|
|
|
* The last part is similar to set_current_item but processing the other way around. |
3286
|
|
|
*/ |
3287
|
|
|
public function previous() |
3288
|
|
|
{ |
3289
|
|
|
$this->last = $this->get_current_item_id(); |
3290
|
|
|
$this->items[$this->last]->save( |
3291
|
|
|
false, |
3292
|
|
|
$this->prerequisites_match($this->last) |
3293
|
|
|
); |
3294
|
|
|
$this->autocomplete_parents($this->last); |
3295
|
|
|
$new_index = $this->get_previous_index(); |
3296
|
|
|
$this->index = $new_index; |
3297
|
|
|
$this->current = $this->ordered_items[$new_index]; |
3298
|
|
|
} |
3299
|
|
|
|
3300
|
|
|
/** |
3301
|
|
|
* Publishes a learnpath. This basically means show or hide the learnpath |
3302
|
|
|
* to normal users. |
3303
|
|
|
* Can be used as abstract. |
3304
|
|
|
* |
3305
|
|
|
* @param int $id Learnpath ID |
3306
|
|
|
* @param int $visibility New visibility (1 = visible/published, 0= invisible/draft) |
3307
|
|
|
* |
3308
|
|
|
* @return bool |
3309
|
|
|
*/ |
3310
|
|
|
public static function toggleVisibility($id, $visibility = 1) |
3311
|
|
|
{ |
3312
|
|
|
$repo = Container::getLpRepository(); |
3313
|
|
|
$lp = $repo->find($id); |
3314
|
|
|
|
3315
|
|
|
if (!$lp) { |
3316
|
|
|
return false; |
3317
|
|
|
} |
3318
|
|
|
|
3319
|
|
|
$visibility = (int) $visibility; |
3320
|
|
|
|
3321
|
|
|
$course = api_get_course_entity(); |
3322
|
|
|
$session = api_get_session_entity(); |
3323
|
|
|
|
3324
|
|
|
if (1 === $visibility) { |
3325
|
|
|
$repo->setVisibilityPublished($lp, $course, $session); |
3326
|
|
|
} else { |
3327
|
|
|
$repo->setVisibilityDraft($lp, $course, $session); |
3328
|
|
|
} |
3329
|
|
|
|
3330
|
|
|
return true; |
3331
|
|
|
} |
3332
|
|
|
|
3333
|
|
|
/** |
3334
|
|
|
* Publishes a learnpath category. |
3335
|
|
|
* This basically means show or hide the learnpath category to normal users. |
3336
|
|
|
* |
3337
|
|
|
* @param int $id |
3338
|
|
|
* @param int $visibility |
3339
|
|
|
* |
3340
|
|
|
* @return bool |
3341
|
|
|
*/ |
3342
|
|
|
public static function toggleCategoryVisibility($id, $visibility = 1) |
3343
|
|
|
{ |
3344
|
|
|
$repo = Container::getLpCategoryRepository(); |
3345
|
|
|
$resource = $repo->find($id); |
3346
|
|
|
|
3347
|
|
|
if (!$resource) { |
3348
|
|
|
return false; |
3349
|
|
|
} |
3350
|
|
|
|
3351
|
|
|
$visibility = (int) $visibility; |
3352
|
|
|
|
3353
|
|
|
$course = api_get_course_entity(); |
3354
|
|
|
$session = api_get_session_entity(); |
3355
|
|
|
|
3356
|
|
|
if (1 === $visibility) { |
3357
|
|
|
$repo->setVisibilityPublished($resource, $course, $session); |
3358
|
|
|
} else { |
3359
|
|
|
$repo->setVisibilityDraft($resource, $course, $session); |
3360
|
|
|
self::toggleCategoryPublish($id, 0); |
3361
|
|
|
} |
3362
|
|
|
|
3363
|
|
|
return false; |
3364
|
|
|
} |
3365
|
|
|
|
3366
|
|
|
/** |
3367
|
|
|
* Publishes a learnpath. This basically means show or hide the learnpath |
3368
|
|
|
* on the course homepage. |
3369
|
|
|
* |
3370
|
|
|
* @param int $id Learnpath id |
3371
|
|
|
* @param string $setVisibility New visibility (v/i - visible/invisible) |
3372
|
|
|
* |
3373
|
|
|
* @return bool |
3374
|
|
|
*/ |
3375
|
|
|
public static function togglePublish($id, $setVisibility = 'v') |
3376
|
|
|
{ |
3377
|
|
|
$addShortcut = false; |
3378
|
|
|
if ('v' === $setVisibility) { |
3379
|
|
|
$addShortcut = true; |
3380
|
|
|
} |
3381
|
|
|
$repo = Container::getLpRepository(); |
3382
|
|
|
/** @var CLp|null $lp */ |
3383
|
|
|
$lp = $repo->find($id); |
3384
|
|
|
if (null === $lp) { |
3385
|
|
|
return false; |
3386
|
|
|
} |
3387
|
|
|
$repoShortcut = Container::getShortcutRepository(); |
3388
|
|
|
if ($addShortcut) { |
3389
|
|
|
$repoShortcut->addShortCut($lp, api_get_user_entity(), api_get_course_entity(), api_get_session_entity()); |
3390
|
|
|
} else { |
3391
|
|
|
$repoShortcut->removeShortCut($lp); |
3392
|
|
|
} |
3393
|
|
|
|
3394
|
|
|
return true; |
3395
|
|
|
} |
3396
|
|
|
|
3397
|
|
|
/** |
3398
|
|
|
* Show or hide the learnpath category on the course homepage. |
3399
|
|
|
* |
3400
|
|
|
* @param int $id |
3401
|
|
|
* @param int $setVisibility |
3402
|
|
|
* |
3403
|
|
|
* @return bool |
3404
|
|
|
*/ |
3405
|
|
|
public static function toggleCategoryPublish($id, $setVisibility = 1) |
3406
|
|
|
{ |
3407
|
|
|
$setVisibility = (int) $setVisibility; |
3408
|
|
|
$addShortcut = false; |
3409
|
|
|
if (1 === $setVisibility) { |
3410
|
|
|
$addShortcut = true; |
3411
|
|
|
} |
3412
|
|
|
|
3413
|
|
|
$repo = Container::getLpCategoryRepository(); |
3414
|
|
|
/** @var CLpCategory|null $lp */ |
3415
|
|
|
$category = $repo->find($id); |
3416
|
|
|
|
3417
|
|
|
if (null === $category) { |
3418
|
|
|
return false; |
3419
|
|
|
} |
3420
|
|
|
|
3421
|
|
|
$repoShortcut = Container::getShortcutRepository(); |
3422
|
|
|
if ($addShortcut) { |
3423
|
|
|
$courseEntity = api_get_course_entity(api_get_course_int_id()); |
3424
|
|
|
$repoShortcut->addShortCut($category, api_get_user_entity(), $courseEntity, api_get_session_entity()); |
3425
|
|
|
} else { |
3426
|
|
|
$repoShortcut->removeShortCut($category); |
3427
|
|
|
} |
3428
|
|
|
|
3429
|
|
|
return true; |
3430
|
|
|
} |
3431
|
|
|
|
3432
|
|
|
/** |
3433
|
|
|
* Check if the learnpath category is visible for a user. |
3434
|
|
|
* |
3435
|
|
|
* @return bool |
3436
|
|
|
*/ |
3437
|
|
|
public static function categoryIsVisibleForStudent( |
3438
|
|
|
CLpCategory $category, |
3439
|
|
|
User $user, |
3440
|
|
|
Course $course, |
3441
|
|
|
SessionEntity $session = null |
3442
|
|
|
) { |
3443
|
|
|
$isAllowedToEdit = api_is_allowed_to_edit(null, true); |
3444
|
|
|
|
3445
|
|
|
if ($isAllowedToEdit) { |
3446
|
|
|
return true; |
3447
|
|
|
} |
3448
|
|
|
|
3449
|
|
|
$categoryVisibility = $category->isVisible($course, $session); |
3450
|
|
|
|
3451
|
|
|
if (!$categoryVisibility) { |
3452
|
|
|
return false; |
3453
|
|
|
} |
3454
|
|
|
|
3455
|
|
|
$subscriptionSettings = self::getSubscriptionSettings(); |
3456
|
|
|
|
3457
|
|
|
if (false === $subscriptionSettings['allow_add_users_to_lp_category']) { |
3458
|
|
|
return true; |
3459
|
|
|
} |
3460
|
|
|
|
3461
|
|
|
$noUserSubscribed = false; |
3462
|
|
|
$noGroupSubscribed = true; |
3463
|
|
|
$users = $category->getUsers(); |
3464
|
|
|
if (empty($users) || !$users->count()) { |
3465
|
|
|
$noUserSubscribed = true; |
3466
|
|
|
} elseif ($category->hasUserAdded($user)) { |
3467
|
|
|
return true; |
3468
|
|
|
} |
3469
|
|
|
|
3470
|
|
|
//$groups = GroupManager::getAllGroupPerUserSubscription($user->getId()); |
3471
|
|
|
|
3472
|
|
|
return $noGroupSubscribed && $noUserSubscribed; |
3473
|
|
|
} |
3474
|
|
|
|
3475
|
|
|
/** |
3476
|
|
|
* Check if a learnpath category is published as course tool. |
3477
|
|
|
* |
3478
|
|
|
* @param int $courseId |
3479
|
|
|
* |
3480
|
|
|
* @return bool |
3481
|
|
|
*/ |
3482
|
|
|
public static function categoryIsPublished(CLpCategory $category, $courseId) |
3483
|
|
|
{ |
3484
|
|
|
return false; |
3485
|
|
|
$link = self::getCategoryLinkForTool($category->getId()); |
|
|
|
|
3486
|
|
|
$em = Database::getManager(); |
3487
|
|
|
|
3488
|
|
|
$tools = $em |
3489
|
|
|
->createQuery(" |
3490
|
|
|
SELECT t FROM ChamiloCourseBundle:CTool t |
3491
|
|
|
WHERE t.course = :course AND |
3492
|
|
|
t.name = :name AND |
3493
|
|
|
t.image LIKE 'lp_category.%' AND |
3494
|
|
|
t.link LIKE :link |
3495
|
|
|
") |
3496
|
|
|
->setParameters([ |
3497
|
|
|
'course' => $courseId, |
3498
|
|
|
'name' => strip_tags($category->getTitle()), |
3499
|
|
|
'link' => "$link%", |
3500
|
|
|
]) |
3501
|
|
|
->getResult(); |
3502
|
|
|
|
3503
|
|
|
/** @var CTool $tool */ |
3504
|
|
|
$tool = current($tools); |
3505
|
|
|
|
3506
|
|
|
return $tool ? $tool->getVisibility() : false; |
3507
|
|
|
} |
3508
|
|
|
|
3509
|
|
|
/** |
3510
|
|
|
* Restart the whole learnpath. Return the URL of the first element. |
3511
|
|
|
* Make sure the results are saved with anoter method. This method should probably be redefined in children classes. |
3512
|
|
|
* To use a similar method statically, use the create_new_attempt() method. |
3513
|
|
|
* |
3514
|
|
|
* @return bool |
3515
|
|
|
*/ |
3516
|
|
|
public function restart() |
3517
|
|
|
{ |
3518
|
|
|
if ($this->debug > 0) { |
3519
|
|
|
error_log('In learnpath::restart()', 0); |
3520
|
|
|
} |
3521
|
|
|
// TODO |
3522
|
|
|
// Call autosave method to save the current progress. |
3523
|
|
|
//$this->index = 0; |
3524
|
|
|
if (api_is_invitee()) { |
3525
|
|
|
return false; |
3526
|
|
|
} |
3527
|
|
|
$session_id = api_get_session_id(); |
3528
|
|
|
$course_id = api_get_course_int_id(); |
3529
|
|
|
$lp_view_table = Database::get_course_table(TABLE_LP_VIEW); |
3530
|
|
|
$sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) |
3531
|
|
|
VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)"; |
3532
|
|
|
if ($this->debug > 2) { |
3533
|
|
|
error_log('Inserting new lp_view for restart: '.$sql, 0); |
3534
|
|
|
} |
3535
|
|
|
Database::query($sql); |
3536
|
|
|
$view_id = Database::insert_id(); |
3537
|
|
|
|
3538
|
|
|
if ($view_id) { |
3539
|
|
|
$this->lp_view_id = $view_id; |
3540
|
|
|
$this->attempt = $this->attempt + 1; |
3541
|
|
|
} else { |
3542
|
|
|
$this->error = 'Could not insert into item_view table...'; |
3543
|
|
|
|
3544
|
|
|
return false; |
3545
|
|
|
} |
3546
|
|
|
$this->autocomplete_parents($this->current); |
3547
|
|
|
foreach ($this->items as $index => $dummy) { |
3548
|
|
|
$this->items[$index]->restart(); |
3549
|
|
|
$this->items[$index]->set_lp_view($this->lp_view_id); |
3550
|
|
|
} |
3551
|
|
|
$this->first(); |
3552
|
|
|
|
3553
|
|
|
return true; |
3554
|
|
|
} |
3555
|
|
|
|
3556
|
|
|
/** |
3557
|
|
|
* Saves the current item. |
3558
|
|
|
* |
3559
|
|
|
* @return bool |
3560
|
|
|
*/ |
3561
|
|
|
public function save_current() |
3562
|
|
|
{ |
3563
|
|
|
$debug = $this->debug; |
3564
|
|
|
// TODO: Do a better check on the index pointing to the right item (it is supposed to be working |
3565
|
|
|
// on $ordered_items[] but not sure it's always safe to use with $items[]). |
3566
|
|
|
if ($debug) { |
3567
|
|
|
error_log('save_current() saving item '.$this->current, 0); |
3568
|
|
|
error_log(''.print_r($this->items, true), 0); |
3569
|
|
|
} |
3570
|
|
|
if (isset($this->items[$this->current]) && |
3571
|
|
|
is_object($this->items[$this->current]) |
3572
|
|
|
) { |
3573
|
|
|
if ($debug) { |
3574
|
|
|
error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime()); |
3575
|
|
|
} |
3576
|
|
|
|
3577
|
|
|
$res = $this->items[$this->current]->save( |
3578
|
|
|
false, |
3579
|
|
|
$this->prerequisites_match($this->current) |
3580
|
|
|
); |
3581
|
|
|
$this->autocomplete_parents($this->current); |
3582
|
|
|
$status = $this->items[$this->current]->get_status(); |
3583
|
|
|
$this->update_queue[$this->current] = $status; |
3584
|
|
|
|
3585
|
|
|
if ($debug) { |
3586
|
|
|
error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime()); |
3587
|
|
|
} |
3588
|
|
|
|
3589
|
|
|
return $res; |
3590
|
|
|
} |
3591
|
|
|
|
3592
|
|
|
return false; |
3593
|
|
|
} |
3594
|
|
|
|
3595
|
|
|
/** |
3596
|
|
|
* Saves the given item. |
3597
|
|
|
* |
3598
|
|
|
* @param int $item_id Optional (will take from $_REQUEST if null) |
3599
|
|
|
* @param bool $from_outside Save from url params (true) or from current attributes (false). Default true |
3600
|
|
|
* |
3601
|
|
|
* @return bool |
3602
|
|
|
*/ |
3603
|
|
|
public function save_item($item_id = null, $from_outside = true) |
3604
|
|
|
{ |
3605
|
|
|
$debug = $this->debug; |
3606
|
|
|
if ($debug) { |
3607
|
|
|
error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0); |
3608
|
|
|
} |
3609
|
|
|
// TODO: Do a better check on the index pointing to the right item (it is supposed to be working |
3610
|
|
|
// on $ordered_items[] but not sure it's always safe to use with $items[]). |
3611
|
|
|
if (empty($item_id)) { |
3612
|
|
|
$item_id = (int) $_REQUEST['id']; |
3613
|
|
|
} |
3614
|
|
|
|
3615
|
|
|
if (empty($item_id)) { |
3616
|
|
|
$item_id = $this->get_current_item_id(); |
3617
|
|
|
} |
3618
|
|
|
if (isset($this->items[$item_id]) && |
3619
|
|
|
is_object($this->items[$item_id]) |
3620
|
|
|
) { |
3621
|
|
|
if ($debug) { |
3622
|
|
|
error_log('Object exists'); |
3623
|
|
|
} |
3624
|
|
|
|
3625
|
|
|
// Saving the item. |
3626
|
|
|
$res = $this->items[$item_id]->save( |
3627
|
|
|
$from_outside, |
3628
|
|
|
$this->prerequisites_match($item_id) |
3629
|
|
|
); |
3630
|
|
|
|
3631
|
|
|
if ($debug) { |
3632
|
|
|
error_log('update_queue before:'); |
3633
|
|
|
error_log(print_r($this->update_queue, 1)); |
3634
|
|
|
} |
3635
|
|
|
$this->autocomplete_parents($item_id); |
3636
|
|
|
|
3637
|
|
|
$status = $this->items[$item_id]->get_status(); |
3638
|
|
|
$this->update_queue[$item_id] = $status; |
3639
|
|
|
|
3640
|
|
|
if ($debug) { |
3641
|
|
|
error_log('get_status(): '.$status); |
3642
|
|
|
error_log('update_queue after:'); |
3643
|
|
|
error_log(print_r($this->update_queue, 1)); |
3644
|
|
|
} |
3645
|
|
|
|
3646
|
|
|
return $res; |
3647
|
|
|
} |
3648
|
|
|
|
3649
|
|
|
return false; |
3650
|
|
|
} |
3651
|
|
|
|
3652
|
|
|
/** |
3653
|
|
|
* Saves the last item seen's ID only in case. |
3654
|
|
|
*/ |
3655
|
|
|
public function save_last() |
3656
|
|
|
{ |
3657
|
|
|
$course_id = api_get_course_int_id(); |
3658
|
|
|
$debug = $this->debug; |
3659
|
|
|
if ($debug) { |
3660
|
|
|
error_log('In learnpath::save_last()', 0); |
3661
|
|
|
} |
3662
|
|
|
$session_condition = api_get_session_condition( |
3663
|
|
|
api_get_session_id(), |
3664
|
|
|
true, |
3665
|
|
|
false |
3666
|
|
|
); |
3667
|
|
|
$table = Database::get_course_table(TABLE_LP_VIEW); |
3668
|
|
|
|
3669
|
|
|
$userId = $this->get_user_id(); |
3670
|
|
|
if (empty($userId)) { |
3671
|
|
|
$userId = api_get_user_id(); |
3672
|
|
|
if ($debug) { |
3673
|
|
|
error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__); |
3674
|
|
|
} |
3675
|
|
|
} |
3676
|
|
|
if (isset($this->current) && !api_is_invitee()) { |
3677
|
|
|
if ($debug) { |
3678
|
|
|
error_log('Saving current item ('.$this->current.') for later review', 0); |
3679
|
|
|
} |
3680
|
|
|
$sql = "UPDATE $table SET |
3681
|
|
|
last_item = ".$this->get_current_item_id()." |
3682
|
|
|
WHERE |
3683
|
|
|
c_id = $course_id AND |
3684
|
|
|
lp_id = ".$this->get_id()." AND |
3685
|
|
|
user_id = ".$userId." ".$session_condition; |
3686
|
|
|
|
3687
|
|
|
if ($debug) { |
3688
|
|
|
error_log('Saving last item seen : '.$sql, 0); |
3689
|
|
|
} |
3690
|
|
|
Database::query($sql); |
3691
|
|
|
} |
3692
|
|
|
|
3693
|
|
|
if (!api_is_invitee()) { |
3694
|
|
|
// Save progress. |
3695
|
|
|
[$progress] = $this->get_progress_bar_text('%'); |
3696
|
|
|
$scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable')); |
3697
|
|
|
$scoreAsProgress = $this->getUseScoreAsProgress(); |
3698
|
|
|
if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) { |
3699
|
|
|
if ($debug) { |
3700
|
|
|
error_log("Return false: Dont save score: $score"); |
3701
|
|
|
error_log("progress: $progress"); |
3702
|
|
|
} |
3703
|
|
|
|
3704
|
|
|
return false; |
3705
|
|
|
} |
3706
|
|
|
|
3707
|
|
|
if ($scoreAsProgress && $scoreAsProgressSetting) { |
3708
|
|
|
$storedProgress = self::getProgress( |
3709
|
|
|
$this->get_id(), |
3710
|
|
|
$userId, |
3711
|
|
|
$course_id, |
3712
|
|
|
$this->get_lp_session_id() |
3713
|
|
|
); |
3714
|
|
|
|
3715
|
|
|
// Check if the stored progress is higher than the new value |
3716
|
|
|
if ($storedProgress >= $progress) { |
3717
|
|
|
if ($debug) { |
3718
|
|
|
error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]"); |
3719
|
|
|
} |
3720
|
|
|
|
3721
|
|
|
return false; |
3722
|
|
|
} |
3723
|
|
|
} |
3724
|
|
|
if ($progress >= 0 && $progress <= 100) { |
3725
|
|
|
$progress = (int) $progress; |
3726
|
|
|
$sql = "UPDATE $table SET |
3727
|
|
|
progress = $progress |
3728
|
|
|
WHERE |
3729
|
|
|
c_id = $course_id AND |
3730
|
|
|
lp_id = ".$this->get_id()." AND |
3731
|
|
|
user_id = ".$userId." ".$session_condition; |
3732
|
|
|
// Ignore errors as some tables might not have the progress field just yet. |
3733
|
|
|
Database::query($sql); |
3734
|
|
|
$this->progress_db = $progress; |
3735
|
|
|
|
3736
|
|
|
if (100 == $progress) { |
3737
|
|
|
Container::getEventDispatcher()->dispatch( |
3738
|
|
|
new LearningPathEndedEvent(['lp_view_id' => $this->lp_view_id]), |
3739
|
|
|
Events::LP_ENDED |
3740
|
|
|
); |
3741
|
|
|
} |
3742
|
|
|
} |
3743
|
|
|
} |
3744
|
|
|
} |
3745
|
|
|
|
3746
|
|
|
/** |
3747
|
|
|
* Sets the current item ID (checks if valid and authorized first). |
3748
|
|
|
* |
3749
|
|
|
* @param int $item_id New item ID. If not given or not authorized, defaults to current |
3750
|
|
|
*/ |
3751
|
|
|
public function set_current_item($item_id = null) |
3752
|
|
|
{ |
3753
|
|
|
$debug = $this->debug; |
3754
|
|
|
if ($debug) { |
3755
|
|
|
error_log('In learnpath::set_current_item('.$item_id.')', 0); |
3756
|
|
|
} |
3757
|
|
|
if (empty($item_id)) { |
3758
|
|
|
if ($debug) { |
3759
|
|
|
error_log('No new current item given, ignore...', 0); |
3760
|
|
|
} |
3761
|
|
|
// Do nothing. |
3762
|
|
|
} else { |
3763
|
|
|
if ($debug) { |
3764
|
|
|
error_log('New current item given is '.$item_id.'...', 0); |
3765
|
|
|
} |
3766
|
|
|
if (is_numeric($item_id)) { |
3767
|
|
|
$item_id = (int) $item_id; |
3768
|
|
|
// TODO: Check in database here. |
3769
|
|
|
$this->last = $this->current; |
3770
|
|
|
$this->current = $item_id; |
3771
|
|
|
// TODO: Update $this->index as well. |
3772
|
|
|
foreach ($this->ordered_items as $index => $item) { |
3773
|
|
|
if ($item == $this->current) { |
3774
|
|
|
$this->index = $index; |
3775
|
|
|
break; |
3776
|
|
|
} |
3777
|
|
|
} |
3778
|
|
|
if ($debug) { |
3779
|
|
|
error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index); |
3780
|
|
|
} |
3781
|
|
|
} else { |
3782
|
|
|
if ($debug) { |
3783
|
|
|
error_log('set_current_item('.$item_id.') failed. Not a numeric value: '); |
3784
|
|
|
} |
3785
|
|
|
} |
3786
|
|
|
} |
3787
|
|
|
} |
3788
|
|
|
|
3789
|
|
|
/** |
3790
|
|
|
* Set index specified prefix terms for all items in this path. |
3791
|
|
|
* |
3792
|
|
|
* @param string $terms_string Comma-separated list of terms |
3793
|
|
|
* @param string $prefix Xapian term prefix |
3794
|
|
|
* |
3795
|
|
|
* @return bool False on error, true otherwise |
3796
|
|
|
*/ |
3797
|
|
|
public function set_terms_by_prefix($terms_string, $prefix) |
3798
|
|
|
{ |
3799
|
|
|
$course_id = api_get_course_int_id(); |
3800
|
|
|
if ('true' !== api_get_setting('search_enabled')) { |
3801
|
|
|
return false; |
3802
|
|
|
} |
3803
|
|
|
|
3804
|
|
|
if (!extension_loaded('xapian')) { |
3805
|
|
|
return false; |
3806
|
|
|
} |
3807
|
|
|
|
3808
|
|
|
$terms_string = trim($terms_string); |
3809
|
|
|
$terms = explode(',', $terms_string); |
3810
|
|
|
array_walk($terms, 'trim_value'); |
3811
|
|
|
$stored_terms = $this->get_common_index_terms_by_prefix($prefix); |
3812
|
|
|
|
3813
|
|
|
// Don't do anything if no change, verify only at DB, not the search engine. |
3814
|
|
|
if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) { |
3815
|
|
|
return false; |
3816
|
|
|
} |
3817
|
|
|
|
3818
|
|
|
require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API. |
3819
|
|
|
require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php'; |
3820
|
|
|
|
3821
|
|
|
$items_table = Database::get_course_table(TABLE_LP_ITEM); |
3822
|
|
|
// TODO: Make query secure agains XSS : use member attr instead of post var. |
3823
|
|
|
$lp_id = (int) $_POST['lp_id']; |
3824
|
|
|
$sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id"; |
3825
|
|
|
$result = Database::query($sql); |
3826
|
|
|
$di = new ChamiloIndexer(); |
3827
|
|
|
|
3828
|
|
|
while ($lp_item = Database::fetch_array($result)) { |
3829
|
|
|
// Get search_did. |
3830
|
|
|
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); |
3831
|
|
|
$sql = 'SELECT * FROM %s |
3832
|
|
|
WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d |
3833
|
|
|
LIMIT 1'; |
3834
|
|
|
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']); |
3835
|
|
|
|
3836
|
|
|
//echo $sql; echo '<br>'; |
3837
|
|
|
$res = Database::query($sql); |
3838
|
|
|
if (Database::num_rows($res) > 0) { |
3839
|
|
|
$se_ref = Database::fetch_array($res); |
3840
|
|
|
// Compare terms. |
3841
|
|
|
$doc = $di->get_document($se_ref['search_did']); |
3842
|
|
|
$xapian_terms = xapian_get_doc_terms($doc, $prefix); |
3843
|
|
|
$xterms = []; |
3844
|
|
|
foreach ($xapian_terms as $xapian_term) { |
3845
|
|
|
$xterms[] = substr($xapian_term['name'], 1); |
3846
|
|
|
} |
3847
|
|
|
|
3848
|
|
|
$dterms = $terms; |
3849
|
|
|
$missing_terms = array_diff($dterms, $xterms); |
3850
|
|
|
$deprecated_terms = array_diff($xterms, $dterms); |
3851
|
|
|
|
3852
|
|
|
// Save it to search engine. |
3853
|
|
|
foreach ($missing_terms as $term) { |
3854
|
|
|
$doc->add_term($prefix.$term, 1); |
3855
|
|
|
} |
3856
|
|
|
foreach ($deprecated_terms as $term) { |
3857
|
|
|
$doc->remove_term($prefix.$term); |
3858
|
|
|
} |
3859
|
|
|
$di->getDb()->replace_document((int) $se_ref['search_did'], $doc); |
3860
|
|
|
$di->getDb()->flush(); |
3861
|
|
|
} |
3862
|
|
|
} |
3863
|
|
|
|
3864
|
|
|
return true; |
3865
|
|
|
} |
3866
|
|
|
|
3867
|
|
|
/** |
3868
|
|
|
* Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item. |
3869
|
|
|
* |
3870
|
|
|
* @param int $id DB ID of the item |
3871
|
|
|
*/ |
3872
|
|
|
public function set_previous_item($id) |
3873
|
|
|
{ |
3874
|
|
|
if ($this->debug > 0) { |
3875
|
|
|
error_log('In learnpath::set_previous_item()', 0); |
3876
|
|
|
} |
3877
|
|
|
$this->last = $id; |
3878
|
|
|
} |
3879
|
|
|
|
3880
|
|
|
/** |
3881
|
|
|
* Sets and saves the expired_on date. |
3882
|
|
|
* |
3883
|
|
|
* @return bool Returns true if author's name is not empty |
3884
|
|
|
*/ |
3885
|
|
|
public function set_modified_on() |
3886
|
|
|
{ |
3887
|
|
|
$this->modified_on = api_get_utc_datetime(); |
3888
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
3889
|
|
|
$lp_id = $this->get_id(); |
3890
|
|
|
$sql = "UPDATE $table SET modified_on = '".$this->modified_on."' |
3891
|
|
|
WHERE iid = $lp_id"; |
3892
|
|
|
Database::query($sql); |
3893
|
|
|
|
3894
|
|
|
return true; |
3895
|
|
|
} |
3896
|
|
|
|
3897
|
|
|
/** |
3898
|
|
|
* Sets the object's error message. |
3899
|
|
|
* |
3900
|
|
|
* @param string $error Error message. If empty, reinits the error string |
3901
|
|
|
*/ |
3902
|
|
|
public function set_error_msg($error = '') |
3903
|
|
|
{ |
3904
|
|
|
if ($this->debug > 0) { |
3905
|
|
|
error_log('In learnpath::set_error_msg()', 0); |
3906
|
|
|
} |
3907
|
|
|
if (empty($error)) { |
3908
|
|
|
$this->error = ''; |
3909
|
|
|
} else { |
3910
|
|
|
$this->error .= $error; |
3911
|
|
|
} |
3912
|
|
|
} |
3913
|
|
|
|
3914
|
|
|
/** |
3915
|
|
|
* Launches the current item if not 'sco' |
3916
|
|
|
* (starts timer and make sure there is a record ready in the DB). |
3917
|
|
|
* |
3918
|
|
|
* @param bool $allow_new_attempt Whether to allow a new attempt or not |
3919
|
|
|
* |
3920
|
|
|
* @return bool |
3921
|
|
|
*/ |
3922
|
|
|
public function start_current_item($allow_new_attempt = false) |
3923
|
|
|
{ |
3924
|
|
|
$debug = $this->debug; |
3925
|
|
|
if ($debug) { |
3926
|
|
|
error_log('In learnpath::start_current_item()'); |
3927
|
|
|
error_log('current: '.$this->current); |
3928
|
|
|
} |
3929
|
|
|
if (0 != $this->current && isset($this->items[$this->current]) && |
3930
|
|
|
is_object($this->items[$this->current]) |
3931
|
|
|
) { |
3932
|
|
|
$type = $this->get_type(); |
3933
|
|
|
$item_type = $this->items[$this->current]->get_type(); |
3934
|
|
|
if ($debug) { |
3935
|
|
|
error_log('item type: '.$item_type); |
3936
|
|
|
error_log('lp type: '.$type); |
3937
|
|
|
} |
3938
|
|
|
if ((2 == $type && 'sco' !== $item_type) || |
3939
|
|
|
(3 == $type && 'au' !== $item_type) || |
3940
|
|
|
(1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type) |
3941
|
|
|
) { |
3942
|
|
|
$this->items[$this->current]->open($allow_new_attempt); |
3943
|
|
|
$this->autocomplete_parents($this->current); |
3944
|
|
|
$prereq_check = $this->prerequisites_match($this->current); |
3945
|
|
|
if ($debug) { |
3946
|
|
|
error_log('start_current_item will save item with prereq: '.$prereq_check); |
3947
|
|
|
} |
3948
|
|
|
$this->items[$this->current]->save(false, $prereq_check); |
3949
|
|
|
} |
3950
|
|
|
// If sco, then it is supposed to have been updated by some other call. |
3951
|
|
|
if ('sco' === $item_type) { |
3952
|
|
|
$this->items[$this->current]->restart(); |
3953
|
|
|
} |
3954
|
|
|
} |
3955
|
|
|
if ($debug) { |
3956
|
|
|
error_log('lp_view_session_id'); |
3957
|
|
|
error_log($this->lp_view_session_id); |
3958
|
|
|
error_log('api session id'); |
3959
|
|
|
error_log(api_get_session_id()); |
3960
|
|
|
error_log('End of learnpath::start_current_item()'); |
3961
|
|
|
} |
3962
|
|
|
|
3963
|
|
|
return true; |
3964
|
|
|
} |
3965
|
|
|
|
3966
|
|
|
/** |
3967
|
|
|
* Stops the processing and counters for the old item (as held in $this->last). |
3968
|
|
|
* |
3969
|
|
|
* @return bool True/False |
3970
|
|
|
*/ |
3971
|
|
|
public function stop_previous_item() |
3972
|
|
|
{ |
3973
|
|
|
$debug = $this->debug; |
3974
|
|
|
if ($debug) { |
3975
|
|
|
error_log('In learnpath::stop_previous_item()', 0); |
3976
|
|
|
} |
3977
|
|
|
|
3978
|
|
|
if (0 != $this->last && $this->last != $this->current && |
3979
|
|
|
isset($this->items[$this->last]) && is_object($this->items[$this->last]) |
3980
|
|
|
) { |
3981
|
|
|
if ($debug) { |
3982
|
|
|
error_log('In learnpath::stop_previous_item() - '.$this->last.' is object'); |
3983
|
|
|
} |
3984
|
|
|
switch ($this->get_type()) { |
3985
|
|
|
case '3': |
3986
|
|
|
if ('au' != $this->items[$this->last]->get_type()) { |
3987
|
|
|
if ($debug) { |
3988
|
|
|
error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au'); |
3989
|
|
|
} |
3990
|
|
|
$this->items[$this->last]->close(); |
3991
|
|
|
} else { |
3992
|
|
|
if ($debug) { |
3993
|
|
|
error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals'); |
3994
|
|
|
} |
3995
|
|
|
} |
3996
|
|
|
break; |
3997
|
|
|
case '2': |
3998
|
|
|
if ('sco' != $this->items[$this->last]->get_type()) { |
3999
|
|
|
if ($debug) { |
4000
|
|
|
error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco'); |
4001
|
|
|
} |
4002
|
|
|
$this->items[$this->last]->close(); |
4003
|
|
|
} else { |
4004
|
|
|
if ($debug) { |
4005
|
|
|
error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals'); |
4006
|
|
|
} |
4007
|
|
|
} |
4008
|
|
|
break; |
4009
|
|
|
case '1': |
4010
|
|
|
default: |
4011
|
|
|
if ($debug) { |
4012
|
|
|
error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset'); |
4013
|
|
|
} |
4014
|
|
|
$this->items[$this->last]->close(); |
4015
|
|
|
break; |
4016
|
|
|
} |
4017
|
|
|
} else { |
4018
|
|
|
if ($debug) { |
4019
|
|
|
error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...'); |
4020
|
|
|
} |
4021
|
|
|
|
4022
|
|
|
return false; |
4023
|
|
|
} |
4024
|
|
|
|
4025
|
|
|
return true; |
4026
|
|
|
} |
4027
|
|
|
|
4028
|
|
|
/** |
4029
|
|
|
* Updates the default view mode from fullscreen to embedded and inversely. |
4030
|
|
|
* |
4031
|
|
|
* @return string The current default view mode ('fullscreen' or 'embedded') |
4032
|
|
|
*/ |
4033
|
|
|
public function update_default_view_mode() |
4034
|
|
|
{ |
4035
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4036
|
|
|
$sql = "SELECT * FROM $table |
4037
|
|
|
WHERE iid = ".$this->get_id(); |
4038
|
|
|
$res = Database::query($sql); |
4039
|
|
|
if (Database::num_rows($res) > 0) { |
4040
|
|
|
$row = Database::fetch_array($res); |
4041
|
|
|
$default_view_mode = $row['default_view_mod']; |
4042
|
|
|
$view_mode = $default_view_mode; |
4043
|
|
|
switch ($default_view_mode) { |
4044
|
|
|
case 'fullscreen': // default with popup |
4045
|
|
|
$view_mode = 'embedded'; |
4046
|
|
|
break; |
4047
|
|
|
case 'embedded': // default view with left menu |
4048
|
|
|
$view_mode = 'embedframe'; |
4049
|
|
|
break; |
4050
|
|
|
case 'embedframe': //folded menu |
4051
|
|
|
$view_mode = 'impress'; |
4052
|
|
|
break; |
4053
|
|
|
case 'impress': |
4054
|
|
|
$view_mode = 'fullscreen'; |
4055
|
|
|
break; |
4056
|
|
|
} |
4057
|
|
|
$sql = "UPDATE $table SET default_view_mod = '$view_mode' |
4058
|
|
|
WHERE iid = ".$this->get_id(); |
4059
|
|
|
Database::query($sql); |
4060
|
|
|
$this->mode = $view_mode; |
4061
|
|
|
|
4062
|
|
|
return $view_mode; |
4063
|
|
|
} |
4064
|
|
|
|
4065
|
|
|
return -1; |
4066
|
|
|
} |
4067
|
|
|
|
4068
|
|
|
/** |
4069
|
|
|
* Updates the default behaviour about auto-commiting SCORM updates. |
4070
|
|
|
* |
4071
|
|
|
* @return bool True if auto-commit has been set to 'on', false otherwise |
4072
|
|
|
*/ |
4073
|
|
|
public function update_default_scorm_commit() |
4074
|
|
|
{ |
4075
|
|
|
$lp_table = Database::get_course_table(TABLE_LP_MAIN); |
4076
|
|
|
$sql = "SELECT * FROM $lp_table |
4077
|
|
|
WHERE iid = ".$this->get_id(); |
4078
|
|
|
$res = Database::query($sql); |
4079
|
|
|
if (Database::num_rows($res) > 0) { |
4080
|
|
|
$row = Database::fetch_array($res); |
4081
|
|
|
$force = $row['force_commit']; |
4082
|
|
|
if (1 == $force) { |
4083
|
|
|
$force = 0; |
4084
|
|
|
$force_return = false; |
4085
|
|
|
} elseif (0 == $force) { |
4086
|
|
|
$force = 1; |
4087
|
|
|
$force_return = true; |
4088
|
|
|
} |
4089
|
|
|
$sql = "UPDATE $lp_table SET force_commit = $force |
4090
|
|
|
WHERE iid = ".$this->get_id(); |
4091
|
|
|
Database::query($sql); |
4092
|
|
|
$this->force_commit = $force_return; |
4093
|
|
|
|
4094
|
|
|
return $force_return; |
4095
|
|
|
} |
4096
|
|
|
|
4097
|
|
|
return -1; |
4098
|
|
|
} |
4099
|
|
|
|
4100
|
|
|
/** |
4101
|
|
|
* Updates the order of learning paths (goes through all of them by order and fills the gaps). |
4102
|
|
|
* |
4103
|
|
|
* @return bool True on success, false on failure |
4104
|
|
|
*/ |
4105
|
|
|
public function update_display_order() |
4106
|
|
|
{ |
4107
|
|
|
return; |
4108
|
|
|
$course_id = api_get_course_int_id(); |
|
|
|
|
4109
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4110
|
|
|
$sql = "SELECT * FROM $table |
4111
|
|
|
WHERE c_id = $course_id |
4112
|
|
|
ORDER BY display_order"; |
4113
|
|
|
$res = Database::query($sql); |
4114
|
|
|
if (false === $res) { |
4115
|
|
|
return false; |
4116
|
|
|
} |
4117
|
|
|
|
4118
|
|
|
$num = Database::num_rows($res); |
4119
|
|
|
// First check the order is correct, globally (might be wrong because |
4120
|
|
|
// of versions < 1.8.4). |
4121
|
|
|
if ($num > 0) { |
4122
|
|
|
$i = 1; |
4123
|
|
|
while ($row = Database::fetch_array($res)) { |
4124
|
|
|
if ($row['display_order'] != $i) { |
4125
|
|
|
// If we find a gap in the order, we need to fix it. |
4126
|
|
|
$sql = "UPDATE $table SET display_order = $i |
4127
|
|
|
WHERE iid = ".$row['iid']; |
4128
|
|
|
Database::query($sql); |
4129
|
|
|
} |
4130
|
|
|
$i++; |
4131
|
|
|
} |
4132
|
|
|
} |
4133
|
|
|
|
4134
|
|
|
return true; |
4135
|
|
|
} |
4136
|
|
|
|
4137
|
|
|
/** |
4138
|
|
|
* Updates the "prevent_reinit" value that enables control on reinitialising items on second view. |
4139
|
|
|
* |
4140
|
|
|
* @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case) |
4141
|
|
|
*/ |
4142
|
|
|
public function update_reinit() |
4143
|
|
|
{ |
4144
|
|
|
$force = $this->prevent_reinit; |
4145
|
|
|
if (1 == $force) { |
4146
|
|
|
$force = 0; |
4147
|
|
|
} elseif (0 == $force) { |
4148
|
|
|
$force = 1; |
4149
|
|
|
} |
4150
|
|
|
|
4151
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4152
|
|
|
$sql = "UPDATE $table SET prevent_reinit = $force |
4153
|
|
|
WHERE iid = ".$this->get_id(); |
4154
|
|
|
Database::query($sql); |
4155
|
|
|
$this->prevent_reinit = $force; |
4156
|
|
|
|
4157
|
|
|
return $force; |
4158
|
|
|
} |
4159
|
|
|
|
4160
|
|
|
/** |
4161
|
|
|
* Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag. |
4162
|
|
|
* |
4163
|
|
|
* @return string 'single', 'multi' or 'seriousgame' |
4164
|
|
|
* |
4165
|
|
|
* @author ndiechburg <[email protected]> |
4166
|
|
|
*/ |
4167
|
|
|
public function get_attempt_mode() |
4168
|
|
|
{ |
4169
|
|
|
//Set default value for seriousgame_mode |
4170
|
|
|
if (!isset($this->seriousgame_mode)) { |
4171
|
|
|
$this->seriousgame_mode = 0; |
4172
|
|
|
} |
4173
|
|
|
// Set default value for prevent_reinit |
4174
|
|
|
if (!isset($this->prevent_reinit)) { |
4175
|
|
|
$this->prevent_reinit = 1; |
4176
|
|
|
} |
4177
|
|
|
if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) { |
4178
|
|
|
return 'seriousgame'; |
4179
|
|
|
} |
4180
|
|
|
if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) { |
4181
|
|
|
return 'single'; |
4182
|
|
|
} |
4183
|
|
|
if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) { |
4184
|
|
|
return 'multiple'; |
4185
|
|
|
} |
4186
|
|
|
|
4187
|
|
|
return 'single'; |
4188
|
|
|
} |
4189
|
|
|
|
4190
|
|
|
/** |
4191
|
|
|
* Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags. |
4192
|
|
|
* |
4193
|
|
|
* @param string 'seriousgame', 'single' or 'multiple' |
|
|
|
|
4194
|
|
|
* |
4195
|
|
|
* @return bool |
4196
|
|
|
* |
4197
|
|
|
* @author ndiechburg <[email protected]> |
4198
|
|
|
*/ |
4199
|
|
|
public function set_attempt_mode($mode) |
4200
|
|
|
{ |
4201
|
|
|
switch ($mode) { |
4202
|
|
|
case 'seriousgame': |
4203
|
|
|
$sg_mode = 1; |
4204
|
|
|
$prevent_reinit = 1; |
4205
|
|
|
break; |
4206
|
|
|
case 'single': |
4207
|
|
|
$sg_mode = 0; |
4208
|
|
|
$prevent_reinit = 1; |
4209
|
|
|
break; |
4210
|
|
|
case 'multiple': |
4211
|
|
|
$sg_mode = 0; |
4212
|
|
|
$prevent_reinit = 0; |
4213
|
|
|
break; |
4214
|
|
|
default: |
4215
|
|
|
$sg_mode = 0; |
4216
|
|
|
$prevent_reinit = 0; |
4217
|
|
|
break; |
4218
|
|
|
} |
4219
|
|
|
$this->prevent_reinit = $prevent_reinit; |
4220
|
|
|
$this->seriousgame_mode = $sg_mode; |
4221
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4222
|
|
|
$sql = "UPDATE $table SET |
4223
|
|
|
prevent_reinit = $prevent_reinit , |
4224
|
|
|
seriousgame_mode = $sg_mode |
4225
|
|
|
WHERE iid = ".$this->get_id(); |
4226
|
|
|
$res = Database::query($sql); |
4227
|
|
|
if ($res) { |
4228
|
|
|
return true; |
4229
|
|
|
} else { |
4230
|
|
|
return false; |
4231
|
|
|
} |
4232
|
|
|
} |
4233
|
|
|
|
4234
|
|
|
/** |
4235
|
|
|
* Switch between multiple attempt, single attempt or serious_game mode (only for scorm). |
4236
|
|
|
* |
4237
|
|
|
* @author ndiechburg <[email protected]> |
4238
|
|
|
*/ |
4239
|
|
|
public function switch_attempt_mode() |
4240
|
|
|
{ |
4241
|
|
|
$mode = $this->get_attempt_mode(); |
4242
|
|
|
switch ($mode) { |
4243
|
|
|
case 'single': |
4244
|
|
|
$next_mode = 'multiple'; |
4245
|
|
|
break; |
4246
|
|
|
case 'multiple': |
4247
|
|
|
$next_mode = 'seriousgame'; |
4248
|
|
|
break; |
4249
|
|
|
case 'seriousgame': |
4250
|
|
|
default: |
4251
|
|
|
$next_mode = 'single'; |
4252
|
|
|
break; |
4253
|
|
|
} |
4254
|
|
|
$this->set_attempt_mode($next_mode); |
4255
|
|
|
} |
4256
|
|
|
|
4257
|
|
|
/** |
4258
|
|
|
* Switch the lp in ktm mode. This is a special scorm mode with unique attempt |
4259
|
|
|
* but possibility to do again a completed item. |
4260
|
|
|
* |
4261
|
|
|
* @return bool true if seriousgame_mode has been set to 1, false otherwise |
4262
|
|
|
* |
4263
|
|
|
* @author ndiechburg <[email protected]> |
4264
|
|
|
*/ |
4265
|
|
|
public function set_seriousgame_mode() |
4266
|
|
|
{ |
4267
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4268
|
|
|
$force = $this->seriousgame_mode; |
4269
|
|
|
if (1 == $force) { |
4270
|
|
|
$force = 0; |
4271
|
|
|
} elseif (0 == $force) { |
4272
|
|
|
$force = 1; |
4273
|
|
|
} |
4274
|
|
|
$sql = "UPDATE $table SET seriousgame_mode = $force |
4275
|
|
|
WHERE iid = ".$this->get_id(); |
4276
|
|
|
Database::query($sql); |
4277
|
|
|
$this->seriousgame_mode = $force; |
4278
|
|
|
|
4279
|
|
|
return $force; |
4280
|
|
|
} |
4281
|
|
|
|
4282
|
|
|
/** |
4283
|
|
|
* Updates the "scorm_debug" value that shows or hide the debug window. |
4284
|
|
|
* |
4285
|
|
|
* @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case) |
4286
|
|
|
*/ |
4287
|
|
|
public function update_scorm_debug() |
4288
|
|
|
{ |
4289
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
4290
|
|
|
$force = $this->scorm_debug; |
4291
|
|
|
if (1 == $force) { |
4292
|
|
|
$force = 0; |
4293
|
|
|
} elseif (0 == $force) { |
4294
|
|
|
$force = 1; |
4295
|
|
|
} |
4296
|
|
|
$sql = "UPDATE $table SET debug = $force |
4297
|
|
|
WHERE iid = ".$this->get_id(); |
4298
|
|
|
Database::query($sql); |
4299
|
|
|
$this->scorm_debug = $force; |
4300
|
|
|
|
4301
|
|
|
return $force; |
4302
|
|
|
} |
4303
|
|
|
|
4304
|
|
|
/** |
4305
|
|
|
* Function that creates a html list of learning path items so that we can add audio files to them. |
4306
|
|
|
* |
4307
|
|
|
* @author Kevin Van Den Haute |
4308
|
|
|
* |
4309
|
|
|
* @return string |
4310
|
|
|
*/ |
4311
|
|
|
public function overview() |
4312
|
|
|
{ |
4313
|
|
|
$return = ''; |
4314
|
|
|
$update_audio = $_GET['updateaudio'] ?? null; |
4315
|
|
|
|
4316
|
|
|
// we need to start a form when we want to update all the mp3 files |
4317
|
|
|
if ('true' == $update_audio) { |
4318
|
|
|
$return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS( |
4319
|
|
|
$_GET['updateaudio'] |
4320
|
|
|
).'&action='.Security::remove_XSS( |
4321
|
|
|
$_GET['action'] |
4322
|
|
|
).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">'; |
4323
|
|
|
} |
4324
|
|
|
$return .= '<div id="message"></div>'; |
4325
|
|
|
if (0 == count($this->items)) { |
4326
|
|
|
$return .= Display::return_message( |
4327
|
|
|
get_lang( |
4328
|
|
|
'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them' |
4329
|
|
|
), |
4330
|
|
|
'normal' |
4331
|
|
|
); |
4332
|
|
|
} else { |
4333
|
|
|
$return_audio = '<table class="table table-hover table-striped data_table">'; |
4334
|
|
|
$return_audio .= '<tr>'; |
4335
|
|
|
$return_audio .= '<th width="40%">'.get_lang('Title').'</th>'; |
4336
|
|
|
$return_audio .= '<th>'.get_lang('Audio').'</th>'; |
4337
|
|
|
$return_audio .= '</tr>'; |
4338
|
|
|
|
4339
|
|
|
if ('true' != $update_audio) { |
4340
|
|
|
/*$return .= '<div class="col-md-12">'; |
4341
|
|
|
$return .= self::return_new_tree($update_audio); |
4342
|
|
|
$return .= '</div>';*/ |
4343
|
|
|
$return .= Display::div( |
4344
|
|
|
Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn--primary']), |
4345
|
|
|
['style' => 'float:left; margin-top:15px;width:100%'] |
4346
|
|
|
); |
4347
|
|
|
} else { |
4348
|
|
|
//$return_audio .= self::return_new_tree($update_audio); |
4349
|
|
|
$return .= $return_audio.'</table>'; |
4350
|
|
|
} |
4351
|
|
|
|
4352
|
|
|
// We need to close the form when we are updating the mp3 files. |
4353
|
|
|
if ('true' == $update_audio) { |
4354
|
|
|
$return .= '<div class="footer-audio">'; |
4355
|
|
|
$return .= Display::button( |
4356
|
|
|
'save_audio', |
4357
|
|
|
'<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'), |
4358
|
|
|
['class' => 'btn btn--primary', 'type' => 'submit'] |
4359
|
|
|
); |
4360
|
|
|
$return .= '</div>'; |
4361
|
|
|
} |
4362
|
|
|
} |
4363
|
|
|
|
4364
|
|
|
// We need to close the form when we are updating the mp3 files. |
4365
|
|
|
if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) { |
4366
|
|
|
$return .= '</form>'; |
4367
|
|
|
} |
4368
|
|
|
|
4369
|
|
|
return $return; |
4370
|
|
|
} |
4371
|
|
|
|
4372
|
|
|
public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null) |
4373
|
|
|
{ |
4374
|
|
|
$sureToDelete = trim(get_lang('Are you sure to delete?')); |
4375
|
|
|
$ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq(); |
4376
|
|
|
|
4377
|
|
|
$content = ' |
4378
|
|
|
<script> |
4379
|
|
|
/* |
4380
|
|
|
Script to manipulate Learning Path items with Drag and drop |
4381
|
|
|
*/ |
4382
|
|
|
$(function() { |
4383
|
|
|
function refreshTree() { |
4384
|
|
|
var params = "&a=get_lp_item_tree"; |
4385
|
|
|
$.get( |
4386
|
|
|
"'.$ajax_url.'", |
4387
|
|
|
params, |
4388
|
|
|
function(result) { |
4389
|
|
|
serialized = []; |
4390
|
|
|
$("#lp_item_list").html(result); |
4391
|
|
|
nestedSortable(); |
4392
|
|
|
} |
4393
|
|
|
); |
4394
|
|
|
} |
4395
|
|
|
|
4396
|
|
|
const nestedQuery = ".nested-sortable"; |
4397
|
|
|
const identifier = "id"; |
4398
|
|
|
const root = document.getElementById("lp_item_list"); |
4399
|
|
|
|
4400
|
|
|
var serialized = []; |
4401
|
|
|
function serialize(sortable) { |
4402
|
|
|
var children = [].slice.call(sortable.children); |
4403
|
|
|
for (var i in children) { |
4404
|
|
|
var nested = children[i].querySelector(nestedQuery); |
4405
|
|
|
var parentId = $(children[i]).parent().parent().attr("id"); |
4406
|
|
|
var id = children[i].dataset[identifier]; |
4407
|
|
|
if (typeof id === "undefined") { |
4408
|
|
|
return; |
4409
|
|
|
} |
4410
|
|
|
serialized.push({ |
4411
|
|
|
id: children[i].dataset[identifier], |
4412
|
|
|
parent_id: parentId |
4413
|
|
|
}); |
4414
|
|
|
|
4415
|
|
|
if (nested) { |
4416
|
|
|
serialize(nested); |
4417
|
|
|
} |
4418
|
|
|
} |
4419
|
|
|
|
4420
|
|
|
return serialized; |
4421
|
|
|
} |
4422
|
|
|
|
4423
|
|
|
function nestedSortable() { |
4424
|
|
|
let left = document.getElementsByClassName("nested-sortable"); |
4425
|
|
|
Array.prototype.forEach.call(left, function(resource) { |
4426
|
|
|
Sortable.create(resource, { |
4427
|
|
|
group: "nested", |
4428
|
|
|
put: ["nested-sortable", ".lp_resource", ".nested-source"], |
4429
|
|
|
animation: 150, |
4430
|
|
|
//fallbackOnBody: true, |
4431
|
|
|
swapThreshold: 0.65, |
4432
|
|
|
dataIdAttr: "data-id", |
4433
|
|
|
store: { |
4434
|
|
|
set: function (sortable) { |
4435
|
|
|
var order = sortable.toArray(); |
4436
|
|
|
console.log(order); |
4437
|
|
|
} |
4438
|
|
|
}, |
4439
|
|
|
onEnd: function(evt) { |
4440
|
|
|
console.log("onEnd"); |
4441
|
|
|
let list = serialize(root); |
4442
|
|
|
let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list); |
4443
|
|
|
$.get( |
4444
|
|
|
"'.$ajax_url.'", |
4445
|
|
|
order, |
4446
|
|
|
function(reponse) { |
4447
|
|
|
$("#message").html(reponse); |
4448
|
|
|
refreshTree(); |
4449
|
|
|
} |
4450
|
|
|
); |
4451
|
|
|
}, |
4452
|
|
|
}); |
4453
|
|
|
}); |
4454
|
|
|
} |
4455
|
|
|
|
4456
|
|
|
nestedSortable(); |
4457
|
|
|
|
4458
|
|
|
let resources = document.getElementsByClassName("lp_resource"); |
4459
|
|
|
Array.prototype.forEach.call(resources, function(resource) { |
4460
|
|
|
Sortable.create(resource, { |
4461
|
|
|
group: "nested", |
4462
|
|
|
put: ["nested-sortable"], |
4463
|
|
|
filter: ".disable_drag", |
4464
|
|
|
animation: 150, |
4465
|
|
|
fallbackOnBody: true, |
4466
|
|
|
swapThreshold: 0.65, |
4467
|
|
|
dataIdAttr: "data-id", |
4468
|
|
|
onRemove: function(evt) { |
4469
|
|
|
console.log("onRemove"); |
4470
|
|
|
var itemEl = evt.item; |
4471
|
|
|
var newIndex = evt.newIndex; |
4472
|
|
|
var id = $(itemEl).attr("id"); |
4473
|
|
|
var parent_id = $(itemEl).parent().parent().attr("id"); |
4474
|
|
|
var type = $(itemEl).find(".link_with_id").attr("data_type"); |
4475
|
|
|
var title = $(itemEl).find(".link_with_id").text(); |
4476
|
|
|
|
4477
|
|
|
let previousId = 0; |
4478
|
|
|
if (0 !== newIndex) { |
4479
|
|
|
previousId = $(itemEl).prev().attr("id"); |
4480
|
|
|
} |
4481
|
|
|
var params = { |
4482
|
|
|
"a": "add_lp_item", |
4483
|
|
|
"id": id, |
4484
|
|
|
"parent_id": parent_id, |
4485
|
|
|
"previous_id": previousId, |
4486
|
|
|
"type": type, |
4487
|
|
|
"title" : title |
4488
|
|
|
}; |
4489
|
|
|
console.log(params); |
4490
|
|
|
$.ajax({ |
4491
|
|
|
type: "GET", |
4492
|
|
|
url: "'.$ajax_url.'", |
4493
|
|
|
data: params, |
4494
|
|
|
success: function(itemId) { |
4495
|
|
|
$(itemEl).attr("id", itemId); |
4496
|
|
|
$(itemEl).attr("data-id", itemId); |
4497
|
|
|
let list = serialize(root); |
4498
|
|
|
let listInString = JSON.stringify(list); |
4499
|
|
|
if (typeof listInString === "undefined") { |
4500
|
|
|
listInString = ""; |
4501
|
|
|
} |
4502
|
|
|
let order = "&a=update_lp_item_order&new_order=" + listInString; |
4503
|
|
|
$.get( |
4504
|
|
|
"'.$ajax_url.'", |
4505
|
|
|
order, |
4506
|
|
|
function(reponse) { |
4507
|
|
|
$("#message").html(reponse); |
4508
|
|
|
refreshTree(); |
4509
|
|
|
} |
4510
|
|
|
); |
4511
|
|
|
} |
4512
|
|
|
}); |
4513
|
|
|
}, |
4514
|
|
|
}); |
4515
|
|
|
}); |
4516
|
|
|
}); |
4517
|
|
|
</script>'; |
4518
|
|
|
|
4519
|
|
|
$content .= " |
4520
|
|
|
<script> |
4521
|
|
|
function confirmation(name) { |
4522
|
|
|
if (confirm('$sureToDelete ' + name)) { |
4523
|
|
|
return true; |
4524
|
|
|
} else { |
4525
|
|
|
return false; |
4526
|
|
|
} |
4527
|
|
|
} |
4528
|
|
|
function refreshTree() { |
4529
|
|
|
var params = '&a=get_lp_item_tree'; |
4530
|
|
|
$.get( |
4531
|
|
|
'".$ajax_url."', |
4532
|
|
|
params, |
4533
|
|
|
function(result) { |
4534
|
|
|
$('#lp_item_list').html(result); |
4535
|
|
|
} |
4536
|
|
|
); |
4537
|
|
|
} |
4538
|
|
|
|
4539
|
|
|
$(function () { |
4540
|
|
|
//$('.scrollbar-inner').scrollbar(); |
4541
|
|
|
/*$('#subtab').on('click', 'a:first', function() { |
4542
|
|
|
window.location.reload(); |
4543
|
|
|
}); |
4544
|
|
|
$('#subtab ').on('click', 'a:first', function () { |
4545
|
|
|
window.location.reload(); |
4546
|
|
|
});*/ |
4547
|
|
|
|
4548
|
|
|
expandColumnToggle('#hide_bar_template', { |
4549
|
|
|
selector: '#lp_sidebar' |
4550
|
|
|
}, { |
4551
|
|
|
selector: '#doc_form' |
4552
|
|
|
}); |
4553
|
|
|
|
4554
|
|
|
$('.lp-btn-associate-forum').on('click', function (e) { |
4555
|
|
|
var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."'); |
4556
|
|
|
if (!associate) { |
4557
|
|
|
e.preventDefault(); |
4558
|
|
|
} |
4559
|
|
|
}); |
4560
|
|
|
|
4561
|
|
|
$('.lp-btn-dissociate-forum').on('click', function (e) { |
4562
|
|
|
var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."'); |
4563
|
|
|
if (!dissociate) { |
4564
|
|
|
e.preventDefault(); |
4565
|
|
|
} |
4566
|
|
|
}); |
4567
|
|
|
|
4568
|
|
|
// hide the current template list for new documment until it tab clicked |
4569
|
|
|
$('#frmModel').hide(); |
4570
|
|
|
}); |
4571
|
|
|
|
4572
|
|
|
// document template for new document tab handler |
4573
|
|
|
/*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) { |
4574
|
|
|
var id = e.target.id; |
4575
|
|
|
if (id == 'subtab2') { |
4576
|
|
|
$('#frmModel').show(); |
4577
|
|
|
} else { |
4578
|
|
|
$('#frmModel').hide(); |
4579
|
|
|
} |
4580
|
|
|
});*/ |
4581
|
|
|
|
4582
|
|
|
function deleteItem(event) { |
4583
|
|
|
var id = $(event).attr('data-id'); |
4584
|
|
|
var title = $(event).attr('data-title'); |
4585
|
|
|
var params = '&a=delete_item&id=' + id; |
4586
|
|
|
if (confirmation(title)) { |
4587
|
|
|
$.get( |
4588
|
|
|
'".$ajax_url."', |
4589
|
|
|
params, |
4590
|
|
|
function(result) { |
4591
|
|
|
refreshTree(); |
4592
|
|
|
} |
4593
|
|
|
); |
4594
|
|
|
} |
4595
|
|
|
} |
4596
|
|
|
</script>"; |
4597
|
|
|
|
4598
|
|
|
$content .= $this->return_new_tree($updateAudio, $dropElementHere); |
4599
|
|
|
$documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0; |
4600
|
|
|
|
4601
|
|
|
$repo = Container::getDocumentRepository(); |
4602
|
|
|
$document = $repo->find($documentId); |
4603
|
|
|
if ($document) { |
4604
|
|
|
// Show the template list |
4605
|
|
|
$content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>'; |
4606
|
|
|
} |
4607
|
|
|
|
4608
|
|
|
// Show the template list. |
4609
|
|
|
if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) { |
4610
|
|
|
// Show the template list. |
4611
|
|
|
$content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>'; |
4612
|
|
|
} |
4613
|
|
|
|
4614
|
|
|
return $content; |
4615
|
|
|
} |
4616
|
|
|
|
4617
|
|
|
/** |
4618
|
|
|
* @param bool $updateAudio |
4619
|
|
|
* @param bool $dropElement |
4620
|
|
|
* |
4621
|
|
|
* @return string |
4622
|
|
|
*/ |
4623
|
|
|
public function return_new_tree($updateAudio = false, $dropElement = false) |
4624
|
|
|
{ |
4625
|
|
|
$list = $this->getBuildTree(false, $dropElement); |
4626
|
|
|
$return = Display::panelCollapse( |
4627
|
|
|
$this->name, |
4628
|
|
|
$list, |
4629
|
|
|
'scorm-list', |
4630
|
|
|
null, |
4631
|
|
|
'scorm-list-accordion', |
4632
|
|
|
'scorm-list-collapse' |
4633
|
|
|
); |
4634
|
|
|
|
4635
|
|
|
if ($updateAudio) { |
4636
|
|
|
//$return = $result['return_audio']; |
4637
|
|
|
} |
4638
|
|
|
|
4639
|
|
|
return $return; |
4640
|
|
|
} |
4641
|
|
|
|
4642
|
|
|
public function getBuildTree($noWrapper = false, $dropElement = false): string |
4643
|
|
|
{ |
4644
|
|
|
$mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq(); |
4645
|
|
|
$upIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon', '', 16, get_lang('Up')); |
4646
|
|
|
$disableUpIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon-disabled', '', 16, get_lang('Up')); |
4647
|
|
|
$downIcon = Display::getMdiIcon('arrow-down-bold', 'ch-tool-icon', '', 16, get_lang('Down')); |
4648
|
|
|
$previewImage = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 16, get_lang('Preview')); |
4649
|
|
|
|
4650
|
|
|
$lpItemRepo = Container::getLpItemRepository(); |
4651
|
|
|
$itemRoot = $lpItemRepo->getRootItem($this->get_id()); |
4652
|
|
|
|
4653
|
|
|
$options = [ |
4654
|
|
|
'decorate' => true, |
4655
|
|
|
'rootOpen' => function($tree) use ($noWrapper) { |
4656
|
|
|
if ($tree[0]['lvl'] === 1) { |
4657
|
|
|
if ($noWrapper) { |
4658
|
|
|
return ''; |
4659
|
|
|
} |
4660
|
|
|
return '<ul id="lp_item_list" class="list-group nested-sortable">'; |
4661
|
|
|
} |
4662
|
|
|
|
4663
|
|
|
return '<ul class="list-group nested-sortable">'; |
4664
|
|
|
}, |
4665
|
|
|
'rootClose' => function($tree) use ($noWrapper, $dropElement) { |
4666
|
|
|
if ($tree[0]['lvl'] === 1) { |
4667
|
|
|
if ($dropElement) { |
4668
|
|
|
//return Display::return_message(get_lang('Drag and drop an element here')); |
4669
|
|
|
//return $this->getDropElementHtml(); |
4670
|
|
|
} |
4671
|
|
|
if ($noWrapper) { |
4672
|
|
|
return ''; |
4673
|
|
|
} |
4674
|
|
|
} |
4675
|
|
|
|
4676
|
|
|
return '</ul>'; |
4677
|
|
|
}, |
4678
|
|
|
'childOpen' => function($child) { |
4679
|
|
|
$id = $child['iid']; |
4680
|
|
|
return '<li |
4681
|
|
|
id="'.$id.'" |
4682
|
|
|
data-id="'.$id.'" |
4683
|
|
|
class=" flex flex-col list-group-item nested-'.$child['lvl'].'">'; |
4684
|
|
|
}, |
4685
|
|
|
'childClose' => '', |
4686
|
|
|
'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) { |
4687
|
|
|
$fullTitle = $node['title']; |
4688
|
|
|
//$title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH); |
4689
|
|
|
$title = $fullTitle; |
4690
|
|
|
$itemId = $node['iid']; |
4691
|
|
|
$type = $node['itemType']; |
4692
|
|
|
$lpId = $this->get_id(); |
4693
|
|
|
|
4694
|
|
|
$moveIcon = ''; |
4695
|
|
|
if (TOOL_LP_FINAL_ITEM !== $type) { |
4696
|
|
|
$moveIcon .= '<a class="moved" href="#">'; |
4697
|
|
|
$moveIcon .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
4698
|
|
|
$moveIcon .= '</a>'; |
4699
|
|
|
} |
4700
|
|
|
|
4701
|
|
|
$iconName = str_replace(' ', '', $type); |
4702
|
|
|
$icon = ''; |
4703
|
|
|
switch ($iconName) { |
4704
|
|
|
case 'category': |
4705
|
|
|
case 'chapter': |
4706
|
|
|
case 'folder': |
4707
|
|
|
case 'dir': |
4708
|
|
|
$icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY); |
4709
|
|
|
break; |
4710
|
|
|
default: |
4711
|
|
|
$icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY); |
4712
|
|
|
break; |
4713
|
|
|
} |
4714
|
|
|
|
4715
|
|
|
$urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId; |
4716
|
|
|
$previewIcon = Display::url( |
4717
|
|
|
$previewImage, |
4718
|
|
|
$urlPreviewLink, |
4719
|
|
|
[ |
4720
|
|
|
'target' => '_blank', |
4721
|
|
|
'class' => 'btn btn--plain', |
4722
|
|
|
'data-title' => $title, |
4723
|
|
|
'title' => $title, |
4724
|
|
|
] |
4725
|
|
|
); |
4726
|
|
|
$url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId; |
4727
|
|
|
|
4728
|
|
|
$preRequisitesIcon = Display::url( |
4729
|
|
|
Display::getMdiIcon('graph', 'ch-tool-icon', '', 16, get_lang('Prerequisites')), |
4730
|
|
|
$url.'&action=edit_item_prereq', |
4731
|
|
|
['class' => ''] |
4732
|
|
|
); |
4733
|
|
|
|
4734
|
|
|
$editIcon = '<a |
4735
|
|
|
href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'" |
4736
|
|
|
class="" |
4737
|
|
|
>'; |
4738
|
|
|
$editIcon .= Display::getMdiIcon('pencil', 'ch-tool-icon', '', 16, get_lang('Edit section description/name')); |
4739
|
|
|
$editIcon .= '</a>'; |
4740
|
|
|
$orderIcons = ''; |
4741
|
|
|
/*if ('final_item' !== $type) { |
4742
|
|
|
$orderIcons = Display::url( |
4743
|
|
|
$upIcon, |
4744
|
|
|
'javascript:void(0)', |
4745
|
|
|
['class' => 'btn btn--plain order_items', 'data-dir' => 'up', 'data-id' => $itemId] |
4746
|
|
|
); |
4747
|
|
|
$orderIcons .= Display::url( |
4748
|
|
|
$downIcon, |
4749
|
|
|
'javascript:void(0)', |
4750
|
|
|
['class' => 'btn btn--plain order_items', 'data-dir' => 'down', 'data-id' => $itemId] |
4751
|
|
|
); |
4752
|
|
|
}*/ |
4753
|
|
|
|
4754
|
|
|
$deleteIcon = ' <a |
4755
|
|
|
data-id = '.$itemId.' |
4756
|
|
|
data-title = \''.addslashes($title).'\' |
4757
|
|
|
href="javascript:void(0);" |
4758
|
|
|
onclick="return deleteItem(this);" |
4759
|
|
|
class="">'; |
4760
|
|
|
$deleteIcon .= Display::getMdiIcon('delete', 'ch-tool-icon', '', 16, get_lang('Delete section')); |
4761
|
|
|
$deleteIcon .= '</a>'; |
4762
|
|
|
$extra = ''; |
4763
|
|
|
|
4764
|
|
|
if ('dir' === $type && empty($node['__children'])) { |
4765
|
|
|
$level = $node['lvl'] + 1; |
4766
|
|
|
$extra = '<ul class="list-group nested-sortable"> |
4767
|
|
|
<li class="list-group-item list-group-item-empty nested-'.$level.'"></li> |
4768
|
|
|
</ul>'; |
4769
|
|
|
} |
4770
|
|
|
|
4771
|
|
|
$buttons = Display::tag( |
4772
|
|
|
'div', |
4773
|
|
|
"<div class=\"btn-group btn-group-sm\"> |
4774
|
|
|
$editIcon |
4775
|
|
|
$preRequisitesIcon |
4776
|
|
|
$orderIcons |
4777
|
|
|
$deleteIcon |
4778
|
|
|
</div>", |
4779
|
|
|
['class' => 'btn-toolbar button_actions'] |
4780
|
|
|
); |
4781
|
|
|
|
4782
|
|
|
return |
4783
|
|
|
"<div class='flex flex-row'> $moveIcon $icon <span class='mx-1'>$title </span></div> |
4784
|
|
|
$extra |
4785
|
|
|
$buttons |
4786
|
|
|
" |
4787
|
|
|
; |
4788
|
|
|
}, |
4789
|
|
|
]; |
4790
|
|
|
|
4791
|
|
|
$tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options); |
4792
|
|
|
|
4793
|
|
|
if (empty($tree) && $dropElement) { |
4794
|
|
|
return $this->getDropElementHtml($noWrapper); |
4795
|
|
|
} |
4796
|
|
|
|
4797
|
|
|
return $tree; |
4798
|
|
|
} |
4799
|
|
|
|
4800
|
|
|
public function getDropElementHtml($noWrapper = false) |
4801
|
|
|
{ |
4802
|
|
|
$li = '<li class="list-group-item">'. |
4803
|
|
|
Display::return_message(get_lang('Drag and drop an element here')). |
4804
|
|
|
'</li>'; |
4805
|
|
|
if ($noWrapper) { |
4806
|
|
|
return $li; |
4807
|
|
|
} |
4808
|
|
|
|
4809
|
|
|
return |
4810
|
|
|
'<ul id="lp_item_list" class="list-group nested-sortable"> |
4811
|
|
|
'.$li.' |
4812
|
|
|
</ul>'; |
4813
|
|
|
} |
4814
|
|
|
|
4815
|
|
|
/** |
4816
|
|
|
* This function builds the action menu. |
4817
|
|
|
* |
4818
|
|
|
* @param bool $returnString Optional |
4819
|
|
|
* @param bool $showRequirementButtons Optional. Allow show the requirements button |
4820
|
|
|
* @param bool $isConfigPage Optional. If is the config page, show the edit button |
4821
|
|
|
* @param bool $allowExpand Optional. Allow show the expand/contract button |
4822
|
|
|
* @param string $action |
4823
|
|
|
* @param array $extraField |
4824
|
|
|
* |
4825
|
|
|
* @return string |
4826
|
|
|
*/ |
4827
|
|
|
public function build_action_menu( |
4828
|
|
|
$returnString = false, |
4829
|
|
|
$showRequirementButtons = true, |
4830
|
|
|
$isConfigPage = false, |
4831
|
|
|
$allowExpand = true, |
4832
|
|
|
$action = '', |
4833
|
|
|
$extraField = [] |
4834
|
|
|
) { |
4835
|
|
|
$actionsRight = ''; |
4836
|
|
|
$lpId = $this->lp_id; |
4837
|
|
|
if (!isset($extraField['backTo']) && empty($extraField['backTo'])) { |
4838
|
|
|
$back = Display::url( |
4839
|
|
|
Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back to learning paths')), |
4840
|
|
|
'lp_controller.php?'.api_get_cidreq() |
4841
|
|
|
); |
4842
|
|
|
} else { |
4843
|
|
|
$back = Display::url( |
4844
|
|
|
Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back')), |
4845
|
|
|
$extraField['backTo'] |
4846
|
|
|
); |
4847
|
|
|
} |
4848
|
|
|
|
4849
|
|
|
/*if ($backToBuild) { |
4850
|
|
|
$back = Display::url( |
4851
|
|
|
Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', null, 32, get_lang('GoBack')), |
4852
|
|
|
"lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq() |
4853
|
|
|
); |
4854
|
|
|
}*/ |
4855
|
|
|
|
4856
|
|
|
$actionsLeft = $back; |
4857
|
|
|
|
4858
|
|
|
$actionsLeft .= Display::url( |
4859
|
|
|
Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 32, get_lang('Preview')), |
4860
|
|
|
'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4861
|
|
|
'action' => 'view', |
4862
|
|
|
'lp_id' => $lpId, |
4863
|
|
|
'isStudentView' => 'true', |
4864
|
|
|
]) |
4865
|
|
|
); |
4866
|
|
|
|
4867
|
|
|
/*$actionsLeft .= Display::url( |
4868
|
|
|
Display::getMdiIcon('music-note-plus', 'ch-tool-icon', null, 32, get_lang('Add audio')), |
4869
|
|
|
'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4870
|
|
|
'action' => 'admin_view', |
4871
|
|
|
'lp_id' => $lpId, |
4872
|
|
|
'updateaudio' => 'true', |
4873
|
|
|
]) |
4874
|
|
|
);*/ |
4875
|
|
|
|
4876
|
|
|
$subscriptionSettings = self::getSubscriptionSettings(); |
4877
|
|
|
|
4878
|
|
|
$request = api_request_uri(); |
4879
|
|
|
if (false === strpos($request, 'edit')) { |
4880
|
|
|
$actionsLeft .= Display::url( |
4881
|
|
|
Display::getMdiIcon('hammer-wrench', 'ch-tool-icon', '', 32, get_lang('Course settings')), |
4882
|
|
|
'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4883
|
|
|
'action' => 'edit', |
4884
|
|
|
'lp_id' => $lpId, |
4885
|
|
|
]) |
4886
|
|
|
); |
4887
|
|
|
} |
4888
|
|
|
|
4889
|
|
|
if ((false === strpos($request, 'build') && |
4890
|
|
|
false === strpos($request, 'add_item')) || |
4891
|
|
|
in_array($action, ['add_audio'], true) |
4892
|
|
|
) { |
4893
|
|
|
$actionsLeft .= Display::url( |
4894
|
|
|
Display::getMdiIcon('pencil', 'ch-tool-icon', '', 32, get_lang('Edit')), |
4895
|
|
|
'lp_controller.php?'.http_build_query([ |
4896
|
|
|
'action' => 'build', |
4897
|
|
|
'lp_id' => $lpId, |
4898
|
|
|
]).'&'.api_get_cidreq() |
4899
|
|
|
); |
4900
|
|
|
} |
4901
|
|
|
|
4902
|
|
|
if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) { |
4903
|
|
|
if (1 == $this->subscribeUsers && |
4904
|
|
|
$subscriptionSettings['allow_add_users_to_lp']) { |
4905
|
|
|
$actionsLeft .= Display::url( |
4906
|
|
|
Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Subscribe users to learning path')), |
4907
|
|
|
api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq() |
4908
|
|
|
); |
4909
|
|
|
} |
4910
|
|
|
} |
4911
|
|
|
|
4912
|
|
|
if ($allowExpand) { |
4913
|
|
|
/*$actionsLeft .= Display::url( |
4914
|
|
|
Display::getMdiIcon('arrow-expand-all', 'ch-tool-icon', null, 32, get_lang('Expand')). |
4915
|
|
|
Display::getMdiIcon('arrow-collapse-all', 'ch-tool-icon', null, 32, get_lang('Collapse')), |
4916
|
|
|
'#', |
4917
|
|
|
['role' => 'button', 'id' => 'hide_bar_template'] |
4918
|
|
|
);*/ |
4919
|
|
|
} |
4920
|
|
|
|
4921
|
|
|
if ($showRequirementButtons) { |
4922
|
|
|
$buttons = [ |
4923
|
|
|
[ |
4924
|
|
|
'title' => get_lang('Set previous step as prerequisite for each step'), |
4925
|
|
|
'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4926
|
|
|
'action' => 'set_previous_step_as_prerequisite', |
4927
|
|
|
'lp_id' => $lpId, |
4928
|
|
|
]), |
4929
|
|
|
], |
4930
|
|
|
[ |
4931
|
|
|
'title' => get_lang('Clear all prerequisites'), |
4932
|
|
|
'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4933
|
|
|
'action' => 'clear_prerequisites', |
4934
|
|
|
'lp_id' => $lpId, |
4935
|
|
|
]), |
4936
|
|
|
], |
4937
|
|
|
]; |
4938
|
|
|
$actionsRight = Display::groupButtonWithDropDown( |
4939
|
|
|
get_lang('Prerequisites options'), |
4940
|
|
|
$buttons, |
4941
|
|
|
true |
4942
|
|
|
); |
4943
|
|
|
} |
4944
|
|
|
|
4945
|
|
|
if (api_is_platform_admin() && isset($extraField['authorlp'])) { |
4946
|
|
|
$actionsLeft .= Display::url( |
4947
|
|
|
Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Author')), |
4948
|
|
|
'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([ |
4949
|
|
|
'action' => 'author_view', |
4950
|
|
|
'lp_id' => $lpId, |
4951
|
|
|
]) |
4952
|
|
|
); |
4953
|
|
|
} |
4954
|
|
|
|
4955
|
|
|
$toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]); |
4956
|
|
|
|
4957
|
|
|
if ($returnString) { |
4958
|
|
|
return $toolbar; |
4959
|
|
|
} |
4960
|
|
|
|
4961
|
|
|
echo $toolbar; |
4962
|
|
|
} |
4963
|
|
|
|
4964
|
|
|
/** |
4965
|
|
|
* Creates the default learning path folder. |
4966
|
|
|
* |
4967
|
|
|
* @param array $course |
4968
|
|
|
* @param int $creatorId |
4969
|
|
|
* |
4970
|
|
|
* @return CDocument |
4971
|
|
|
*/ |
4972
|
|
|
public static function generate_learning_path_folder($course, $creatorId = 0) |
4973
|
|
|
{ |
4974
|
|
|
// Creating learning_path folder |
4975
|
|
|
$dir = 'learning_path'; |
4976
|
|
|
$creatorId = empty($creatorId) ? api_get_user_id() : $creatorId; |
4977
|
|
|
|
4978
|
|
|
return create_unexisting_directory( |
4979
|
|
|
$course, |
4980
|
|
|
$creatorId, |
4981
|
|
|
0, |
4982
|
|
|
null, |
4983
|
|
|
0, |
4984
|
|
|
'', |
4985
|
|
|
$dir, |
4986
|
|
|
get_lang('Learning paths'), |
4987
|
|
|
0 |
4988
|
|
|
); |
4989
|
|
|
} |
4990
|
|
|
|
4991
|
|
|
/** |
4992
|
|
|
* @param array $course |
4993
|
|
|
* @param string $lp_name |
4994
|
|
|
* @param int $creatorId |
4995
|
|
|
* |
4996
|
|
|
* @return CDocument |
4997
|
|
|
*/ |
4998
|
|
|
public function generate_lp_folder($course, $lp_name = '', $creatorId = 0) |
4999
|
|
|
{ |
5000
|
|
|
$filepath = ''; |
5001
|
|
|
$dir = '/learning_path/'; |
5002
|
|
|
|
5003
|
|
|
if (empty($lp_name)) { |
5004
|
|
|
$lp_name = $this->name; |
5005
|
|
|
} |
5006
|
|
|
$creatorId = empty($creatorId) ? api_get_user_id() : $creatorId; |
5007
|
|
|
$parent = self::generate_learning_path_folder($course, $creatorId); |
5008
|
|
|
|
5009
|
|
|
// Limits title size |
5010
|
|
|
$title = api_substr(api_replace_dangerous_char($lp_name), 0, 80); |
5011
|
|
|
$dir = $dir.$title; |
5012
|
|
|
|
5013
|
|
|
// Creating LP folder |
5014
|
|
|
$folder = null; |
5015
|
|
|
if ($parent) { |
5016
|
|
|
$folder = create_unexisting_directory( |
5017
|
|
|
$course, |
5018
|
|
|
$creatorId, |
5019
|
|
|
0, |
5020
|
|
|
0, |
5021
|
|
|
0, |
5022
|
|
|
$filepath, |
5023
|
|
|
$dir, |
5024
|
|
|
$lp_name, |
5025
|
|
|
'', |
5026
|
|
|
false, |
5027
|
|
|
false, |
5028
|
|
|
$parent |
5029
|
|
|
); |
5030
|
|
|
} |
5031
|
|
|
|
5032
|
|
|
return $folder; |
5033
|
|
|
} |
5034
|
|
|
|
5035
|
|
|
/** |
5036
|
|
|
* Create a new document //still needs some finetuning. |
5037
|
|
|
* |
5038
|
|
|
* @param array $courseInfo |
5039
|
|
|
* @param string $content |
5040
|
|
|
* @param string $title |
5041
|
|
|
* @param string $extension |
5042
|
|
|
* @param int $parentId |
5043
|
|
|
* @param int $creatorId creator id |
5044
|
|
|
* |
5045
|
|
|
* @return int |
5046
|
|
|
*/ |
5047
|
|
|
public function create_document( |
5048
|
|
|
$courseInfo, |
5049
|
|
|
$content = '', |
5050
|
|
|
$title = '', |
5051
|
|
|
$extension = 'html', |
5052
|
|
|
$parentId = 0, |
5053
|
|
|
$creatorId = 0 |
5054
|
|
|
) { |
5055
|
|
|
$creatorId = empty($creatorId) ? api_get_user_id() : $creatorId; |
5056
|
|
|
$sessionId = api_get_session_id(); |
5057
|
|
|
|
5058
|
|
|
// Generates folder |
5059
|
|
|
$this->generate_lp_folder($courseInfo); |
5060
|
|
|
// stripslashes() before calling api_replace_dangerous_char() because $_POST['title'] |
5061
|
|
|
// is already escaped twice when it gets here. |
5062
|
|
|
$originalTitle = !empty($title) ? $title : $_POST['title']; |
5063
|
|
|
if (!empty($title)) { |
5064
|
|
|
$title = api_replace_dangerous_char(stripslashes($title)); |
5065
|
|
|
} else { |
5066
|
|
|
$title = api_replace_dangerous_char(stripslashes($_POST['title'])); |
5067
|
|
|
} |
5068
|
|
|
|
5069
|
|
|
$title = disable_dangerous_file($title); |
5070
|
|
|
$filename = $title; |
5071
|
|
|
$tmp_filename = "$filename.$extension"; |
5072
|
|
|
/*$i = 0; |
5073
|
|
|
while (file_exists($filepath.$tmp_filename.'.'.$extension)) { |
5074
|
|
|
$tmp_filename = $filename.'_'.++$i; |
5075
|
|
|
}*/ |
5076
|
|
|
$filename = $tmp_filename.'.'.$extension; |
5077
|
|
|
|
5078
|
|
|
if ('html' === $extension) { |
5079
|
|
|
$content = stripslashes($content); |
5080
|
|
|
$content = str_replace( |
5081
|
|
|
api_get_path(WEB_COURSE_PATH), |
5082
|
|
|
api_get_path(REL_PATH).'courses/', |
5083
|
|
|
$content |
5084
|
|
|
); |
5085
|
|
|
|
5086
|
|
|
// Change the path of mp3 to absolute. |
5087
|
|
|
// The first regexp deals with :// urls. |
5088
|
|
|
/*$content = preg_replace( |
5089
|
|
|
"|(flashvars=\"file=)([^:/]+)/|", |
5090
|
|
|
"$1".api_get_path( |
5091
|
|
|
REL_COURSE_PATH |
5092
|
|
|
).$courseInfo['path'].'/document/', |
5093
|
|
|
$content |
5094
|
|
|
);*/ |
5095
|
|
|
// The second regexp deals with audio/ urls. |
5096
|
|
|
/*$content = preg_replace( |
5097
|
|
|
"|(flashvars=\"file=)([^/]+)/|", |
5098
|
|
|
"$1".api_get_path( |
5099
|
|
|
REL_COURSE_PATH |
5100
|
|
|
).$courseInfo['path'].'/document/$2/', |
5101
|
|
|
$content |
5102
|
|
|
);*/ |
5103
|
|
|
// For flv player: To prevent edition problem with firefox, |
5104
|
|
|
// we have to use a strange tip (don't blame me please). |
5105
|
|
|
$content = str_replace( |
5106
|
|
|
'</body>', |
5107
|
|
|
'<style type="text/css">body{}</style></body>', |
5108
|
|
|
$content |
5109
|
|
|
); |
5110
|
|
|
} |
5111
|
|
|
|
5112
|
|
|
$document = DocumentManager::addDocument( |
5113
|
|
|
$courseInfo, |
5114
|
|
|
null, |
5115
|
|
|
'file', |
5116
|
|
|
'', |
5117
|
|
|
$tmp_filename, |
5118
|
|
|
'', |
5119
|
|
|
0, //readonly |
5120
|
|
|
true, |
5121
|
|
|
null, |
5122
|
|
|
$sessionId, |
5123
|
|
|
$creatorId, |
5124
|
|
|
false, |
5125
|
|
|
$content, |
5126
|
|
|
$parentId |
5127
|
|
|
); |
5128
|
|
|
|
5129
|
|
|
$document_id = $document->getIid(); |
5130
|
|
|
if ($document_id) { |
5131
|
|
|
$new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : ''; |
5132
|
|
|
$new_title = $originalTitle; |
5133
|
|
|
|
5134
|
|
|
if ($new_comment || $new_title) { |
5135
|
|
|
$tbl_doc = Database::get_course_table(TABLE_DOCUMENT); |
5136
|
|
|
$ct = ''; |
5137
|
|
|
if ($new_comment) { |
5138
|
|
|
$ct .= ", comment='".Database::escape_string($new_comment)."'"; |
5139
|
|
|
} |
5140
|
|
|
if ($new_title) { |
5141
|
|
|
$ct .= ", title='".Database::escape_string($new_title)."' "; |
5142
|
|
|
} |
5143
|
|
|
|
5144
|
|
|
$sql = "UPDATE $tbl_doc SET ".substr($ct, 1)." |
5145
|
|
|
WHERE iid = $document_id "; |
5146
|
|
|
Database::query($sql); |
5147
|
|
|
} |
5148
|
|
|
} |
5149
|
|
|
|
5150
|
|
|
return $document_id; |
5151
|
|
|
} |
5152
|
|
|
|
5153
|
|
|
/** |
5154
|
|
|
* Edit a document based on $_POST and $_GET parameters 'dir' and 'path'. |
5155
|
|
|
*/ |
5156
|
|
|
public function edit_document() |
5157
|
|
|
{ |
5158
|
|
|
$repo = Container::getDocumentRepository(); |
5159
|
|
|
if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) { |
5160
|
|
|
$id = (int) $_REQUEST['document_id']; |
5161
|
|
|
/** @var CDocument $document */ |
5162
|
|
|
$document = $repo->find($id); |
5163
|
|
|
if ($document->getResourceNode()->hasEditableTextContent()) { |
5164
|
|
|
$repo->updateResourceFileContent($document, $_REQUEST['content_lp']); |
5165
|
|
|
} |
5166
|
|
|
$document->setTitle($_REQUEST['title']); |
5167
|
|
|
$repo->update($document); |
5168
|
|
|
} |
5169
|
|
|
} |
5170
|
|
|
|
5171
|
|
|
/** |
5172
|
|
|
* Displays the selected item, with a panel for manipulating the item. |
5173
|
|
|
* |
5174
|
|
|
* @param CLpItem $lpItem |
5175
|
|
|
* @param string $msg |
5176
|
|
|
* @param bool $show_actions |
5177
|
|
|
* |
5178
|
|
|
* @return string |
5179
|
|
|
*/ |
5180
|
|
|
public function display_item($lpItem, $msg = null, $show_actions = true) |
5181
|
|
|
{ |
5182
|
|
|
$course_id = api_get_course_int_id(); |
5183
|
|
|
$return = ''; |
5184
|
|
|
|
5185
|
|
|
if (null === $lpItem) { |
5186
|
|
|
return ''; |
5187
|
|
|
} |
5188
|
|
|
$item_id = $lpItem->getIid(); |
5189
|
|
|
$itemType = $lpItem->getItemType(); |
5190
|
|
|
$lpId = $lpItem->getLp()->getIid(); |
5191
|
|
|
$path = $lpItem->getPath(); |
5192
|
|
|
|
5193
|
|
|
Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0); |
5194
|
|
|
|
5195
|
|
|
// Prevents wrong parent selection for document, see Bug#1251. |
5196
|
|
|
if ('dir' !== $itemType) { |
5197
|
|
|
Session::write('parent_item_id', $lpItem->getParentItemId()); |
5198
|
|
|
} |
5199
|
|
|
|
5200
|
|
|
if ($show_actions) { |
5201
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5202
|
|
|
} |
5203
|
|
|
$return .= '<div style="padding:10px;">'; |
5204
|
|
|
|
5205
|
|
|
if ('' != $msg) { |
5206
|
|
|
$return .= $msg; |
5207
|
|
|
} |
5208
|
|
|
|
5209
|
|
|
$return .= '<h3>'.$lpItem->getTitle().'</h3>'; |
5210
|
|
|
|
5211
|
|
|
switch ($itemType) { |
5212
|
|
|
case TOOL_THREAD: |
5213
|
|
|
$link = $this->rl_get_resource_link_for_learnpath( |
5214
|
|
|
$course_id, |
5215
|
|
|
$lpId, |
5216
|
|
|
$item_id, |
5217
|
|
|
0 |
5218
|
|
|
); |
5219
|
|
|
$return .= Display::url( |
5220
|
|
|
get_lang('Go to thread'), |
5221
|
|
|
$link, |
5222
|
|
|
['class' => 'btn btn--primary'] |
5223
|
|
|
); |
5224
|
|
|
break; |
5225
|
|
|
case TOOL_FORUM: |
5226
|
|
|
$return .= Display::url( |
5227
|
|
|
get_lang('Go to the forum'), |
5228
|
|
|
api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path, |
5229
|
|
|
['class' => 'btn btn--primary'] |
5230
|
|
|
); |
5231
|
|
|
break; |
5232
|
|
|
case TOOL_QUIZ: |
5233
|
|
|
if (!empty($path)) { |
5234
|
|
|
$exercise = new Exercise(); |
5235
|
|
|
$exercise->read($path); |
5236
|
|
|
$return .= $exercise->description.'<br />'; |
5237
|
|
|
$return .= Display::url( |
5238
|
|
|
get_lang('Go to exercise'), |
5239
|
|
|
api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id, |
5240
|
|
|
['class' => 'btn btn--primary'] |
5241
|
|
|
); |
5242
|
|
|
} |
5243
|
|
|
break; |
5244
|
|
|
case TOOL_LP_FINAL_ITEM: |
5245
|
|
|
$return .= $this->getSavedFinalItem(); |
5246
|
|
|
break; |
5247
|
|
|
case TOOL_DOCUMENT: |
5248
|
|
|
case TOOL_READOUT_TEXT: |
5249
|
|
|
$repo = Container::getDocumentRepository(); |
5250
|
|
|
/** @var CDocument $document */ |
5251
|
|
|
$document = $repo->find($lpItem->getPath()); |
5252
|
|
|
$return .= $this->display_document($document, true, true); |
5253
|
|
|
break; |
5254
|
|
|
} |
5255
|
|
|
$return .= '</div>'; |
5256
|
|
|
|
5257
|
|
|
return $return; |
5258
|
|
|
} |
5259
|
|
|
|
5260
|
|
|
/** |
5261
|
|
|
* Shows the needed forms for editing a specific item. |
5262
|
|
|
* |
5263
|
|
|
* @param CLpItem $lpItem |
5264
|
|
|
* |
5265
|
|
|
* @throws Exception |
5266
|
|
|
* |
5267
|
|
|
* |
5268
|
|
|
* @return string |
5269
|
|
|
*/ |
5270
|
|
|
public function display_edit_item($lpItem, $excludeExtraFields = []) |
5271
|
|
|
{ |
5272
|
|
|
$return = ''; |
5273
|
|
|
if (empty($lpItem)) { |
5274
|
|
|
return ''; |
5275
|
|
|
} |
5276
|
|
|
$itemType = $lpItem->getItemType(); |
5277
|
|
|
$path = $lpItem->getPath(); |
5278
|
|
|
|
5279
|
|
|
switch ($itemType) { |
5280
|
|
|
case 'dir': |
5281
|
|
|
case 'asset': |
5282
|
|
|
case 'sco': |
5283
|
|
|
if (isset($_GET['view']) && 'build' === $_GET['view']) { |
5284
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5285
|
|
|
$return .= $this->display_item_form($lpItem, 'edit'); |
5286
|
|
|
} else { |
5287
|
|
|
$return .= $this->display_item_form($lpItem, 'edit_item'); |
5288
|
|
|
} |
5289
|
|
|
break; |
5290
|
|
|
case TOOL_LP_FINAL_ITEM: |
5291
|
|
|
case TOOL_DOCUMENT: |
5292
|
|
|
case TOOL_READOUT_TEXT: |
5293
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5294
|
|
|
$return .= $this->displayDocumentForm('edit', $lpItem); |
5295
|
|
|
break; |
5296
|
|
|
case TOOL_LINK: |
5297
|
|
|
$link = null; |
5298
|
|
|
if (!empty($path)) { |
5299
|
|
|
$repo = Container::getLinkRepository(); |
5300
|
|
|
$link = $repo->find($path); |
5301
|
|
|
} |
5302
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5303
|
|
|
$return .= $this->display_link_form('edit', $lpItem, $link); |
5304
|
|
|
|
5305
|
|
|
break; |
5306
|
|
|
case TOOL_QUIZ: |
5307
|
|
|
if (!empty($path)) { |
5308
|
|
|
$repo = Container::getQuizRepository(); |
5309
|
|
|
$resource = $repo->find($path); |
5310
|
|
|
} |
5311
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5312
|
|
|
$return .= $this->display_quiz_form('edit', $lpItem, $resource); |
5313
|
|
|
break; |
5314
|
|
|
case TOOL_STUDENTPUBLICATION: |
5315
|
|
|
if (!empty($path)) { |
5316
|
|
|
$repo = Container::getStudentPublicationRepository(); |
5317
|
|
|
$resource = $repo->find($path); |
5318
|
|
|
} |
5319
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5320
|
|
|
$return .= $this->display_student_publication_form('edit', $lpItem, $resource); |
5321
|
|
|
break; |
5322
|
|
|
case TOOL_FORUM: |
5323
|
|
|
if (!empty($path)) { |
5324
|
|
|
$repo = Container::getForumRepository(); |
5325
|
|
|
$resource = $repo->find($path); |
5326
|
|
|
} |
5327
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5328
|
|
|
$return .= $this->display_forum_form('edit', $lpItem, $resource); |
5329
|
|
|
break; |
5330
|
|
|
case TOOL_THREAD: |
5331
|
|
|
if (!empty($path)) { |
5332
|
|
|
$repo = Container::getForumPostRepository(); |
5333
|
|
|
$resource = $repo->find($path); |
5334
|
|
|
} |
5335
|
|
|
$return .= $this->displayItemMenu($lpItem); |
5336
|
|
|
$return .= $this->display_thread_form('edit', $lpItem, $resource); |
5337
|
|
|
break; |
5338
|
|
|
} |
5339
|
|
|
|
5340
|
|
|
return $return; |
5341
|
|
|
} |
5342
|
|
|
|
5343
|
|
|
/** |
5344
|
|
|
* Function that displays a list with al the resources that |
5345
|
|
|
* could be added to the learning path. |
5346
|
|
|
* |
5347
|
|
|
* @throws Exception |
5348
|
|
|
*/ |
5349
|
|
|
public function displayResources(): string |
5350
|
|
|
{ |
5351
|
|
|
// Get all the docs. |
5352
|
|
|
$documents = $this->get_documents(true); |
5353
|
|
|
|
5354
|
|
|
// Get all the exercises. |
5355
|
|
|
$exercises = $this->get_exercises(); |
5356
|
|
|
|
5357
|
|
|
// Get all the links. |
5358
|
|
|
$links = $this->get_links(); |
5359
|
|
|
|
5360
|
|
|
// Get all the student publications. |
5361
|
|
|
$works = $this->get_student_publications(); |
5362
|
|
|
|
5363
|
|
|
// Get all the forums. |
5364
|
|
|
$forums = $this->get_forums(); |
5365
|
|
|
|
5366
|
|
|
// Get all surveys |
5367
|
|
|
$surveys = $this->getSurveys(); |
5368
|
|
|
|
5369
|
|
|
// Get the final item form (see BT#11048) . |
5370
|
|
|
$finish = $this->getFinalItemForm(); |
5371
|
|
|
$size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG |
5372
|
|
|
$headers = [ |
5373
|
|
|
Display::getMdiIcon('bookshelf', 'ch-tool-icon-gradient', '', 64, get_lang('Documents')), |
5374
|
|
|
Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon-gradient', '', 64, get_lang('Tests')), |
5375
|
|
|
Display::getMdiIcon('file-link', 'ch-tool-icon-gradient', '', 64, get_lang('Links')), |
5376
|
|
|
Display::getMdiIcon('inbox-full', 'ch-tool-icon-gradient', '', 64, get_lang('Assignments')), |
5377
|
|
|
Display::getMdiIcon('comment-quote', 'ch-tool-icon-gradient', '', 64, get_lang('Forums')), |
5378
|
|
|
Display::getMdiIcon('bookmark-multiple', 'ch-tool-icon-gradient', '', 64, get_lang('Add section')), |
5379
|
|
|
Display::getMdiIcon('form-dropdown', 'ch-tool-icon-gradient', '', 64, get_lang('Add survey')), |
5380
|
|
|
Display::getMdiIcon('certificate', 'ch-tool-icon-gradient', '', 64, get_lang('Certificate')), |
5381
|
|
|
]; |
5382
|
|
|
$content = ''; |
5383
|
|
|
/*$content = Display::return_message( |
5384
|
|
|
get_lang('Click on the [Learner view] button to see your learning path'), |
5385
|
|
|
'normal' |
5386
|
|
|
);*/ |
5387
|
|
|
$section = $this->displayNewSectionForm(); |
5388
|
|
|
$selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0; |
5389
|
|
|
|
5390
|
|
|
return Display::tabs( |
5391
|
|
|
$headers, |
5392
|
|
|
[ |
5393
|
|
|
$documents, |
5394
|
|
|
$exercises, |
5395
|
|
|
$links, |
5396
|
|
|
$works, |
5397
|
|
|
$forums, |
5398
|
|
|
$section, |
5399
|
|
|
$surveys, |
5400
|
|
|
$finish, |
5401
|
|
|
], |
5402
|
|
|
'resource_tab', |
5403
|
|
|
[], |
5404
|
|
|
[], |
5405
|
|
|
$selected |
5406
|
|
|
); |
5407
|
|
|
} |
5408
|
|
|
|
5409
|
|
|
/** |
5410
|
|
|
* Returns the extension of a document. |
5411
|
|
|
* |
5412
|
|
|
* @param string $filename |
5413
|
|
|
* |
5414
|
|
|
* @return string Extension (part after the last dot) |
5415
|
|
|
*/ |
5416
|
|
|
public function get_extension($filename) |
5417
|
|
|
{ |
5418
|
|
|
$explode = explode('.', $filename); |
5419
|
|
|
|
5420
|
|
|
return $explode[count($explode) - 1]; |
5421
|
|
|
} |
5422
|
|
|
|
5423
|
|
|
/** |
5424
|
|
|
* @return string |
5425
|
|
|
*/ |
5426
|
|
|
public function getCurrentBuildingModeURL() |
5427
|
|
|
{ |
5428
|
|
|
$pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : ''; |
5429
|
|
|
$action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : ''; |
5430
|
|
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : ''; |
5431
|
|
|
$view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : ''; |
5432
|
|
|
|
5433
|
|
|
$currentUrl = api_get_self().'?'.api_get_cidreq(). |
5434
|
|
|
'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id; |
5435
|
|
|
|
5436
|
|
|
return $currentUrl; |
5437
|
|
|
} |
5438
|
|
|
|
5439
|
|
|
/** |
5440
|
|
|
* Displays a document by id. |
5441
|
|
|
* |
5442
|
|
|
* @param CDocument $document |
5443
|
|
|
* @param bool $show_title |
5444
|
|
|
* @param bool $iframe |
5445
|
|
|
* @param bool $edit_link |
5446
|
|
|
* |
5447
|
|
|
* @return string |
5448
|
|
|
*/ |
5449
|
|
|
public function display_document($document, $show_title = false, $iframe = true, $edit_link = false) |
5450
|
|
|
{ |
5451
|
|
|
$return = ''; |
5452
|
|
|
if (!$document) { |
5453
|
|
|
return ''; |
5454
|
|
|
} |
5455
|
|
|
|
5456
|
|
|
$repo = Container::getDocumentRepository(); |
5457
|
|
|
|
5458
|
|
|
// TODO: Add a path filter. |
5459
|
|
|
if ($iframe) { |
5460
|
|
|
$url = $repo->getResourceFileUrl($document); |
5461
|
|
|
|
5462
|
|
|
$return .= '<iframe |
5463
|
|
|
id="learnpath_preview_frame" |
5464
|
|
|
frameborder="0" |
5465
|
|
|
height="400" |
5466
|
|
|
width="100%" |
5467
|
|
|
scrolling="auto" |
5468
|
|
|
src="'.$url.'"></iframe>'; |
5469
|
|
|
} else { |
5470
|
|
|
$return = $repo->getResourceFileContent($document); |
5471
|
|
|
} |
5472
|
|
|
|
5473
|
|
|
return $return; |
5474
|
|
|
} |
5475
|
|
|
|
5476
|
|
|
/** |
5477
|
|
|
* Return HTML form to add/edit a link item. |
5478
|
|
|
* |
5479
|
|
|
* @param string $action (add/edit) |
5480
|
|
|
* @param CLpItem $lpItem |
5481
|
|
|
* @param CLink $link |
5482
|
|
|
* |
5483
|
|
|
* @throws Exception |
5484
|
|
|
* |
5485
|
|
|
* |
5486
|
|
|
* @return string HTML form |
5487
|
|
|
*/ |
5488
|
|
|
public function display_link_form($action, $lpItem, $link) |
5489
|
|
|
{ |
5490
|
|
|
$item_url = ''; |
5491
|
|
|
if ($link) { |
5492
|
|
|
$item_url = stripslashes($link->getUrl()); |
5493
|
|
|
} |
5494
|
|
|
$form = new FormValidator( |
5495
|
|
|
'edit_link', |
5496
|
|
|
'POST', |
5497
|
|
|
$this->getCurrentBuildingModeURL() |
5498
|
|
|
); |
5499
|
|
|
|
5500
|
|
|
LearnPathItemForm::setForm($form, $action, $this, $lpItem); |
5501
|
|
|
|
5502
|
|
|
$urlAttributes = ['class' => 'learnpath_item_form']; |
5503
|
|
|
$urlAttributes['disabled'] = 'disabled'; |
5504
|
|
|
$form->addElement('url', 'url', get_lang('URL'), $urlAttributes); |
5505
|
|
|
$form->setDefault('url', $item_url); |
5506
|
|
|
|
5507
|
|
|
$form->addButtonSave(get_lang('Save'), 'submit_button'); |
5508
|
|
|
|
5509
|
|
|
return '<div class="sectioncomment">'.$form->returnForm().'</div>'; |
5510
|
|
|
} |
5511
|
|
|
|
5512
|
|
|
/** |
5513
|
|
|
* Return HTML form to add/edit a quiz. |
5514
|
|
|
* |
5515
|
|
|
* @param string $action Action (add/edit) |
5516
|
|
|
* @param CLpItem $lpItem Item ID if already exists |
5517
|
|
|
* @param CQuiz $exercise Extra information (quiz ID if integer) |
5518
|
|
|
* |
5519
|
|
|
* @throws Exception |
5520
|
|
|
* |
5521
|
|
|
* @return string HTML form |
5522
|
|
|
*/ |
5523
|
|
|
public function display_quiz_form($action, $lpItem, $exercise) |
5524
|
|
|
{ |
5525
|
|
|
$form = new FormValidator( |
5526
|
|
|
'quiz_form', |
5527
|
|
|
'POST', |
5528
|
|
|
$this->getCurrentBuildingModeURL() |
5529
|
|
|
); |
5530
|
|
|
|
5531
|
|
|
LearnPathItemForm::setForm($form, $action, $this, $lpItem); |
5532
|
|
|
$form->addButtonSave(get_lang('Save'), 'submit_button'); |
5533
|
|
|
|
5534
|
|
|
return '<div class="sectioncomment">'.$form->returnForm().'</div>'; |
5535
|
|
|
} |
5536
|
|
|
|
5537
|
|
|
/** |
5538
|
|
|
* Return the form to display the forum edit/add option. |
5539
|
|
|
* |
5540
|
|
|
* @param CLpItem $lpItem |
5541
|
|
|
* |
5542
|
|
|
* @throws Exception |
5543
|
|
|
* |
5544
|
|
|
* @return string HTML form |
5545
|
|
|
*/ |
5546
|
|
|
public function display_forum_form($action, $lpItem, $resource) |
5547
|
|
|
{ |
5548
|
|
|
$form = new FormValidator( |
5549
|
|
|
'forum_form', |
5550
|
|
|
'POST', |
5551
|
|
|
$this->getCurrentBuildingModeURL() |
5552
|
|
|
); |
5553
|
|
|
LearnPathItemForm::setForm($form, $action, $this, $lpItem); |
5554
|
|
|
|
5555
|
|
|
if ('add' === $action) { |
5556
|
|
|
$form->addButtonSave(get_lang('Add forum to course'), 'submit_button'); |
5557
|
|
|
} else { |
5558
|
|
|
$form->addButtonSave(get_lang('Edit the current forum'), 'submit_button'); |
5559
|
|
|
} |
5560
|
|
|
|
5561
|
|
|
return '<div class="sectioncomment">'.$form->returnForm().'</div>'; |
5562
|
|
|
} |
5563
|
|
|
|
5564
|
|
|
/** |
5565
|
|
|
* Return HTML form to add/edit forum threads. |
5566
|
|
|
* |
5567
|
|
|
* @param string $action |
5568
|
|
|
* @param CLpItem $lpItem |
5569
|
|
|
* @param string $resource |
5570
|
|
|
* |
5571
|
|
|
* @throws Exception |
5572
|
|
|
* |
5573
|
|
|
* @return string HTML form |
5574
|
|
|
*/ |
5575
|
|
|
public function display_thread_form($action, $lpItem, $resource) |
5576
|
|
|
{ |
5577
|
|
|
$form = new FormValidator( |
5578
|
|
|
'thread_form', |
5579
|
|
|
'POST', |
5580
|
|
|
$this->getCurrentBuildingModeURL() |
5581
|
|
|
); |
5582
|
|
|
|
5583
|
|
|
LearnPathItemForm::setForm($form, 'edit', $this, $lpItem); |
5584
|
|
|
|
5585
|
|
|
$form->addButtonSave(get_lang('Save'), 'submit_button'); |
5586
|
|
|
|
5587
|
|
|
return $form->returnForm(); |
5588
|
|
|
} |
5589
|
|
|
|
5590
|
|
|
/** |
5591
|
|
|
* Return the HTML form to display an item (generally a dir item). |
5592
|
|
|
* |
5593
|
|
|
* @param CLpItem $lpItem |
5594
|
|
|
* @param string $action |
5595
|
|
|
* |
5596
|
|
|
* @throws Exception |
5597
|
|
|
* |
5598
|
|
|
* |
5599
|
|
|
* @return string HTML form |
5600
|
|
|
*/ |
5601
|
|
|
public function display_item_form( |
5602
|
|
|
$lpItem, |
5603
|
|
|
$action = 'add_item' |
5604
|
|
|
) { |
5605
|
|
|
$item_type = $lpItem->getItemType(); |
5606
|
|
|
|
5607
|
|
|
$url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id; |
5608
|
|
|
|
5609
|
|
|
$form = new FormValidator('form_'.$item_type, 'POST', $url); |
5610
|
|
|
LearnPathItemForm::setForm($form, 'edit', $this, $lpItem); |
5611
|
|
|
|
5612
|
|
|
$form->addButtonSave(get_lang('Save section'), 'submit_button'); |
5613
|
|
|
|
5614
|
|
|
return $form->returnForm(); |
5615
|
|
|
} |
5616
|
|
|
|
5617
|
|
|
/** |
5618
|
|
|
* Return HTML form to add/edit a student publication (work). |
5619
|
|
|
* |
5620
|
|
|
* @param string $action |
5621
|
|
|
* @param CStudentPublication $resource |
5622
|
|
|
* |
5623
|
|
|
* @throws Exception |
5624
|
|
|
* |
5625
|
|
|
* @return string HTML form |
5626
|
|
|
*/ |
5627
|
|
|
public function display_student_publication_form($action, CLpItem $lpItem, $resource) |
5628
|
|
|
{ |
5629
|
|
|
$form = new FormValidator('frm_student_publication', 'post', '#'); |
5630
|
|
|
LearnPathItemForm::setForm($form, 'edit', $this, $lpItem); |
5631
|
|
|
|
5632
|
|
|
$form->addButtonSave(get_lang('Save'), 'submit_button'); |
5633
|
|
|
|
5634
|
|
|
$return = '<div class="sectioncomment">'; |
5635
|
|
|
$return .= $form->returnForm(); |
5636
|
|
|
$return .= '</div>'; |
5637
|
|
|
|
5638
|
|
|
return $return; |
5639
|
|
|
} |
5640
|
|
|
|
5641
|
|
|
public function displayNewSectionForm() |
5642
|
|
|
{ |
5643
|
|
|
$action = 'add_item'; |
5644
|
|
|
$item_type = 'dir'; |
5645
|
|
|
|
5646
|
|
|
$lpItem = (new CLpItem()) |
5647
|
|
|
->setTitle('') |
5648
|
|
|
->setItemType('dir') |
5649
|
|
|
; |
5650
|
|
|
|
5651
|
|
|
$url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id; |
5652
|
|
|
|
5653
|
|
|
$form = new FormValidator('form_'.$item_type, 'POST', $url); |
5654
|
|
|
LearnPathItemForm::setForm($form, 'add', $this, $lpItem); |
5655
|
|
|
|
5656
|
|
|
$form->addButtonSave(get_lang('Save section'), 'submit_button'); |
5657
|
|
|
$form->addElement('hidden', 'type', 'dir'); |
5658
|
|
|
|
5659
|
|
|
return $form->returnForm(); |
5660
|
|
|
} |
5661
|
|
|
|
5662
|
|
|
/** |
5663
|
|
|
* Returns the form to update or create a document. |
5664
|
|
|
* |
5665
|
|
|
* @param string $action (add/edit) |
5666
|
|
|
* @param CLpItem $lpItem |
5667
|
|
|
* |
5668
|
|
|
* |
5669
|
|
|
* @throws Exception |
5670
|
|
|
* |
5671
|
|
|
* @return string HTML form |
5672
|
|
|
*/ |
5673
|
|
|
public function displayDocumentForm($action = 'add', $lpItem = null) |
5674
|
|
|
{ |
5675
|
|
|
$courseInfo = api_get_course_info(); |
5676
|
|
|
|
5677
|
|
|
$form = new FormValidator( |
5678
|
|
|
'form', |
5679
|
|
|
'POST', |
5680
|
|
|
$this->getCurrentBuildingModeURL(), |
5681
|
|
|
'', |
5682
|
|
|
['enctype' => 'multipart/form-data'] |
5683
|
|
|
); |
5684
|
|
|
|
5685
|
|
|
$data = $this->generate_lp_folder($courseInfo); |
5686
|
|
|
|
5687
|
|
|
if (null !== $lpItem) { |
5688
|
|
|
LearnPathItemForm::setForm($form, $action, $this, $lpItem); |
5689
|
|
|
} |
5690
|
|
|
|
5691
|
|
|
switch ($action) { |
5692
|
|
|
case 'add': |
5693
|
|
|
$folders = DocumentManager::get_all_document_folders( |
5694
|
|
|
$courseInfo, |
5695
|
|
|
0, |
5696
|
|
|
true |
5697
|
|
|
); |
5698
|
|
|
DocumentManager::build_directory_selector( |
5699
|
|
|
$folders, |
5700
|
|
|
'', |
5701
|
|
|
[], |
5702
|
|
|
true, |
5703
|
|
|
$form, |
5704
|
|
|
'directory_parent_id' |
5705
|
|
|
); |
5706
|
|
|
|
5707
|
|
|
if ($data) { |
5708
|
|
|
$defaults['directory_parent_id'] = $data->getIid(); |
5709
|
|
|
} |
5710
|
|
|
|
5711
|
|
|
break; |
5712
|
|
|
} |
5713
|
|
|
|
5714
|
|
|
$form->addButtonSave(get_lang('Save'), 'submit_button'); |
5715
|
|
|
|
5716
|
|
|
return $form->returnForm(); |
5717
|
|
|
} |
5718
|
|
|
|
5719
|
|
|
/** |
5720
|
|
|
* @param array $courseInfo |
5721
|
|
|
* @param string $content |
5722
|
|
|
* @param string $title |
5723
|
|
|
* @param int $parentId |
5724
|
|
|
* |
5725
|
|
|
* @return int |
5726
|
|
|
*/ |
5727
|
|
|
public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0) |
5728
|
|
|
{ |
5729
|
|
|
$creatorId = api_get_user_id(); |
5730
|
|
|
$sessionId = api_get_session_id(); |
5731
|
|
|
|
5732
|
|
|
// Generates folder |
5733
|
|
|
$result = $this->generate_lp_folder($courseInfo); |
5734
|
|
|
$dir = $result['dir']; |
5735
|
|
|
|
5736
|
|
|
if (empty($parentId) || '/' === $parentId) { |
5737
|
|
|
$postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir; |
5738
|
|
|
$dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting. |
5739
|
|
|
|
5740
|
|
|
if ('/' === $parentId) { |
5741
|
|
|
$dir = '/'; |
5742
|
|
|
} |
5743
|
|
|
|
5744
|
|
|
// Please, do not modify this dirname formatting. |
5745
|
|
|
if (strstr($dir, '..')) { |
5746
|
|
|
$dir = '/'; |
5747
|
|
|
} |
5748
|
|
|
|
5749
|
|
|
if (!empty($dir[0]) && '.' == $dir[0]) { |
5750
|
|
|
$dir = substr($dir, 1); |
5751
|
|
|
} |
5752
|
|
|
if (!empty($dir[0]) && '/' != $dir[0]) { |
5753
|
|
|
$dir = '/'.$dir; |
5754
|
|
|
} |
5755
|
|
|
if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) { |
5756
|
|
|
$dir .= '/'; |
5757
|
|
|
} |
5758
|
|
|
} else { |
5759
|
|
|
$parentInfo = DocumentManager::get_document_data_by_id( |
5760
|
|
|
$parentId, |
5761
|
|
|
$courseInfo['code'] |
5762
|
|
|
); |
5763
|
|
|
if (!empty($parentInfo)) { |
5764
|
|
|
$dir = $parentInfo['path'].'/'; |
5765
|
|
|
} |
5766
|
|
|
} |
5767
|
|
|
|
5768
|
|
|
$filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir; |
5769
|
|
|
|
5770
|
|
|
if (!is_dir($filepath)) { |
5771
|
|
|
$dir = '/'; |
5772
|
|
|
$filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir; |
5773
|
|
|
} |
5774
|
|
|
|
5775
|
|
|
$originalTitle = !empty($title) ? $title : $_POST['title']; |
5776
|
|
|
|
5777
|
|
|
if (!empty($title)) { |
5778
|
|
|
$title = api_replace_dangerous_char(stripslashes($title)); |
5779
|
|
|
} else { |
5780
|
|
|
$title = api_replace_dangerous_char(stripslashes($_POST['title'])); |
5781
|
|
|
} |
5782
|
|
|
|
5783
|
|
|
$title = disable_dangerous_file($title); |
5784
|
|
|
$filename = $title; |
5785
|
|
|
$content = !empty($content) ? $content : $_POST['content_lp']; |
5786
|
|
|
$tmpFileName = $filename; |
5787
|
|
|
|
5788
|
|
|
$i = 0; |
5789
|
|
|
while (file_exists($filepath.$tmpFileName.'.html')) { |
5790
|
|
|
$tmpFileName = $filename.'_'.++$i; |
5791
|
|
|
} |
5792
|
|
|
|
5793
|
|
|
$filename = $tmpFileName.'.html'; |
5794
|
|
|
$content = stripslashes($content); |
5795
|
|
|
|
5796
|
|
|
if (file_exists($filepath.$filename)) { |
5797
|
|
|
return 0; |
5798
|
|
|
} |
5799
|
|
|
|
5800
|
|
|
$putContent = file_put_contents($filepath.$filename, $content); |
5801
|
|
|
|
5802
|
|
|
if (false === $putContent) { |
5803
|
|
|
return 0; |
5804
|
|
|
} |
5805
|
|
|
|
5806
|
|
|
$fileSize = filesize($filepath.$filename); |
5807
|
|
|
$saveFilePath = $dir.$filename; |
5808
|
|
|
|
5809
|
|
|
$document = DocumentManager::addDocument( |
5810
|
|
|
$courseInfo, |
5811
|
|
|
$saveFilePath, |
5812
|
|
|
'file', |
5813
|
|
|
$fileSize, |
5814
|
|
|
$tmpFileName, |
5815
|
|
|
'', |
5816
|
|
|
0, //readonly |
5817
|
|
|
true, |
5818
|
|
|
null, |
5819
|
|
|
$sessionId, |
5820
|
|
|
$creatorId |
5821
|
|
|
); |
5822
|
|
|
|
5823
|
|
|
$documentId = $document->getIid(); |
5824
|
|
|
|
5825
|
|
|
if (!$document) { |
5826
|
|
|
return 0; |
5827
|
|
|
} |
5828
|
|
|
|
5829
|
|
|
$newComment = isset($_POST['comment']) ? trim($_POST['comment']) : ''; |
5830
|
|
|
$newTitle = $originalTitle; |
5831
|
|
|
|
5832
|
|
|
if ($newComment || $newTitle) { |
5833
|
|
|
$em = Database::getManager(); |
5834
|
|
|
|
5835
|
|
|
if ($newComment) { |
5836
|
|
|
$document->setComment($newComment); |
5837
|
|
|
} |
5838
|
|
|
|
5839
|
|
|
if ($newTitle) { |
5840
|
|
|
$document->setTitle($newTitle); |
5841
|
|
|
} |
5842
|
|
|
|
5843
|
|
|
$em->persist($document); |
5844
|
|
|
$em->flush(); |
5845
|
|
|
} |
5846
|
|
|
|
5847
|
|
|
return $documentId; |
5848
|
|
|
} |
5849
|
|
|
|
5850
|
|
|
/** |
5851
|
|
|
* Displays the menu for manipulating a step. |
5852
|
|
|
* |
5853
|
|
|
* @return string |
5854
|
|
|
*/ |
5855
|
|
|
public function displayItemMenu(CLpItem $lpItem) |
5856
|
|
|
{ |
5857
|
|
|
$item_id = $lpItem->getIid(); |
5858
|
|
|
$audio = $lpItem->getAudio(); |
5859
|
|
|
$itemType = $lpItem->getItemType(); |
5860
|
|
|
$path = $lpItem->getPath(); |
5861
|
|
|
|
5862
|
|
|
$return = ''; |
5863
|
|
|
$audio_player = null; |
5864
|
|
|
// We display an audio player if needed. |
5865
|
|
|
if (!empty($audio)) { |
5866
|
|
|
/*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio']; |
5867
|
|
|
$audio_player .= '<div class="lp_mediaplayer" id="container">' |
5868
|
|
|
.'<audio src="'.$webAudioPath.'" controls>' |
5869
|
|
|
.'</div><br>';*/ |
5870
|
|
|
} |
5871
|
|
|
|
5872
|
|
|
$url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id; |
5873
|
|
|
|
5874
|
|
|
if (TOOL_LP_FINAL_ITEM !== $itemType) { |
5875
|
|
|
$return .= Display::url( |
5876
|
|
|
Display::getMdiIcon('pencil', 'ch-tool-icon', null, 22, get_lang('Edit')), |
5877
|
|
|
$url.'&action=edit_item&path_item='.$path |
5878
|
|
|
); |
5879
|
|
|
|
5880
|
|
|
/*$return .= Display::url( |
5881
|
|
|
Display::getMdiIcon('arrow-right-bold', 'ch-tool-icon', null, 22, get_lang('Move')), |
5882
|
|
|
$url.'&action=move_item' |
5883
|
|
|
);*/ |
5884
|
|
|
} |
5885
|
|
|
|
5886
|
|
|
// Commented for now as prerequisites cannot be added to chapters. |
5887
|
|
|
if ('dir' !== $itemType) { |
5888
|
|
|
$return .= Display::url( |
5889
|
|
|
Display::getMdiIcon('graph', 'ch-tool-icon', null, 22, get_lang('Prerequisites')), |
5890
|
|
|
$url.'&action=edit_item_prereq' |
5891
|
|
|
); |
5892
|
|
|
} |
5893
|
|
|
$return .= Display::url( |
5894
|
|
|
Display::getMdiIcon('delete', 'ch-tool-icon', null, 22, get_lang('Delete')), |
5895
|
|
|
$url.'&action=delete_item' |
5896
|
|
|
); |
5897
|
|
|
|
5898
|
|
|
/*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) { |
5899
|
|
|
$documentData = DocumentManager::get_document_data_by_id($path, $course_code); |
5900
|
|
|
if (empty($documentData)) { |
5901
|
|
|
// Try with iid |
5902
|
|
|
$table = Database::get_course_table(TABLE_DOCUMENT); |
5903
|
|
|
$sql = "SELECT path FROM $table |
5904
|
|
|
WHERE |
5905
|
|
|
c_id = ".api_get_course_int_id()." AND |
5906
|
|
|
iid = ".$path." AND |
5907
|
|
|
path NOT LIKE '%_DELETED_%'"; |
5908
|
|
|
$result = Database::query($sql); |
5909
|
|
|
$documentData = Database::fetch_array($result); |
5910
|
|
|
if ($documentData) { |
5911
|
|
|
$documentData['absolute_path_from_document'] = '/document'.$documentData['path']; |
5912
|
|
|
} |
5913
|
|
|
} |
5914
|
|
|
if (isset($documentData['absolute_path_from_document'])) { |
5915
|
|
|
$return .= get_lang('File').': '.$documentData['absolute_path_from_document']; |
5916
|
|
|
} |
5917
|
|
|
}*/ |
5918
|
|
|
|
5919
|
|
|
if (!empty($audio_player)) { |
5920
|
|
|
$return .= $audio_player; |
5921
|
|
|
} |
5922
|
|
|
|
5923
|
|
|
return Display::toolbarAction('lp_item', [$return]); |
5924
|
|
|
} |
5925
|
|
|
|
5926
|
|
|
/** |
5927
|
|
|
* Creates the javascript needed for filling up the checkboxes without page reload. |
5928
|
|
|
* |
5929
|
|
|
* @return string |
5930
|
|
|
*/ |
5931
|
|
|
public function get_js_dropdown_array() |
5932
|
|
|
{ |
5933
|
|
|
$return = 'var child_name = new Array();'."\n"; |
5934
|
|
|
$return .= 'var child_value = new Array();'."\n\n"; |
5935
|
|
|
$return .= 'child_name[0] = new Array();'."\n"; |
5936
|
|
|
$return .= 'child_value[0] = new Array();'."\n\n"; |
5937
|
|
|
|
5938
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
5939
|
|
|
$sql = "SELECT * FROM ".$tbl_lp_item." |
5940
|
|
|
WHERE |
5941
|
|
|
lp_id = ".$this->lp_id." AND |
5942
|
|
|
parent_item_id = 0 |
5943
|
|
|
ORDER BY display_order ASC"; |
5944
|
|
|
Database::query($sql); |
5945
|
|
|
$i = 0; |
5946
|
|
|
|
5947
|
|
|
$list = $this->getItemsForForm(true); |
5948
|
|
|
|
5949
|
|
|
foreach ($list as $row_zero) { |
5950
|
|
|
if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) { |
5951
|
|
|
if (TOOL_QUIZ == $row_zero['item_type']) { |
5952
|
|
|
$row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']); |
5953
|
|
|
} |
5954
|
|
|
$js_var = json_encode(get_lang('After').' '.$row_zero['title']); |
5955
|
|
|
$return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n"; |
5956
|
|
|
$return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n"; |
5957
|
|
|
} |
5958
|
|
|
} |
5959
|
|
|
|
5960
|
|
|
$return .= "\n"; |
5961
|
|
|
$sql = "SELECT * FROM $tbl_lp_item |
5962
|
|
|
WHERE lp_id = ".$this->lp_id; |
5963
|
|
|
$res = Database::query($sql); |
5964
|
|
|
while ($row = Database::fetch_array($res)) { |
5965
|
|
|
$sql_parent = "SELECT * FROM ".$tbl_lp_item." |
5966
|
|
|
WHERE |
5967
|
|
|
parent_item_id = ".$row['iid']." |
5968
|
|
|
ORDER BY display_order ASC"; |
5969
|
|
|
$res_parent = Database::query($sql_parent); |
5970
|
|
|
$i = 0; |
5971
|
|
|
$return .= 'child_name['.$row['iid'].'] = new Array();'."\n"; |
5972
|
|
|
$return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n"; |
5973
|
|
|
|
5974
|
|
|
while ($row_parent = Database::fetch_array($res_parent)) { |
5975
|
|
|
$js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title'])); |
5976
|
|
|
$return .= 'child_name['.$row['iid'].']['.$i.'] = '.$js_var.' ;'."\n"; |
5977
|
|
|
$return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n"; |
5978
|
|
|
} |
5979
|
|
|
$return .= "\n"; |
5980
|
|
|
} |
5981
|
|
|
|
5982
|
|
|
$return .= " |
5983
|
|
|
function load_cbo(id) { |
5984
|
|
|
if (!id) { |
5985
|
|
|
return false; |
5986
|
|
|
} |
5987
|
|
|
|
5988
|
|
|
var cbo = document.getElementById('previous'); |
5989
|
|
|
if (cbo) { |
5990
|
|
|
for(var i = cbo.length - 1; i > 0; i--) { |
5991
|
|
|
cbo.options[i] = null; |
5992
|
|
|
} |
5993
|
|
|
var k=0; |
5994
|
|
|
for (var i = 1; i <= child_name[id].length; i++){ |
5995
|
|
|
var option = new Option(child_name[id][i - 1], child_value[id][i - 1]); |
5996
|
|
|
option.style.paddingLeft = '40px'; |
5997
|
|
|
cbo.options[i] = option; |
5998
|
|
|
k = i; |
5999
|
|
|
} |
6000
|
|
|
cbo.options[k].selected = true; |
6001
|
|
|
} |
6002
|
|
|
|
6003
|
|
|
//$('#previous').selectpicker('refresh'); |
6004
|
|
|
}"; |
6005
|
|
|
|
6006
|
|
|
return $return; |
6007
|
|
|
} |
6008
|
|
|
|
6009
|
|
|
/** |
6010
|
|
|
* Display the form to allow moving an item. |
6011
|
|
|
* |
6012
|
|
|
* @param CLpItem $lpItem |
6013
|
|
|
* |
6014
|
|
|
* @throws Exception |
6015
|
|
|
* |
6016
|
|
|
* |
6017
|
|
|
* @return string HTML form |
6018
|
|
|
*/ |
6019
|
|
|
public function display_move_item($lpItem) |
6020
|
|
|
{ |
6021
|
|
|
$return = ''; |
6022
|
|
|
$path = $lpItem->getPath(); |
6023
|
|
|
|
6024
|
|
|
if ($lpItem) { |
6025
|
|
|
$itemType = $lpItem->getItemType(); |
6026
|
|
|
switch ($itemType) { |
6027
|
|
|
case 'dir': |
6028
|
|
|
case 'asset': |
6029
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6030
|
|
|
$return .= $this->display_item_form( |
6031
|
|
|
$lpItem, |
6032
|
|
|
get_lang('Move the current section'), |
6033
|
|
|
'move', |
6034
|
|
|
$row |
6035
|
|
|
); |
6036
|
|
|
break; |
6037
|
|
|
case TOOL_DOCUMENT: |
6038
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6039
|
|
|
$return .= $this->displayDocumentForm('move', $lpItem); |
6040
|
|
|
break; |
6041
|
|
|
case TOOL_LINK: |
6042
|
|
|
$link = null; |
6043
|
|
|
if (!empty($path)) { |
6044
|
|
|
$repo = Container::getLinkRepository(); |
6045
|
|
|
$link = $repo->find($path); |
6046
|
|
|
} |
6047
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6048
|
|
|
$return .= $this->display_link_form('move', $lpItem, $link); |
6049
|
|
|
break; |
6050
|
|
|
case TOOL_HOTPOTATOES: |
6051
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6052
|
|
|
$return .= $this->display_link_form('move', $lpItem, $row); |
6053
|
|
|
break; |
6054
|
|
|
case TOOL_QUIZ: |
6055
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6056
|
|
|
$return .= $this->display_quiz_form('move', $lpItem, $row); |
6057
|
|
|
break; |
6058
|
|
|
case TOOL_STUDENTPUBLICATION: |
6059
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6060
|
|
|
$return .= $this->display_student_publication_form('move', $lpItem, $row); |
6061
|
|
|
break; |
6062
|
|
|
case TOOL_FORUM: |
6063
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6064
|
|
|
$return .= $this->display_forum_form('move', $lpItem, $row); |
6065
|
|
|
break; |
6066
|
|
|
case TOOL_THREAD: |
6067
|
|
|
$return .= $this->displayItemMenu($lpItem); |
6068
|
|
|
$return .= $this->display_forum_form('move', $lpItem, $row); |
6069
|
|
|
break; |
6070
|
|
|
} |
6071
|
|
|
} |
6072
|
|
|
|
6073
|
|
|
return $return; |
6074
|
|
|
} |
6075
|
|
|
|
6076
|
|
|
/** |
6077
|
|
|
* Return HTML form to allow prerequisites selection. |
6078
|
|
|
* |
6079
|
|
|
* @todo use FormValidator |
6080
|
|
|
* |
6081
|
|
|
* @return string HTML form |
6082
|
|
|
*/ |
6083
|
|
|
public function displayItemPrerequisitesForm(CLpItem $lpItem) |
6084
|
|
|
{ |
6085
|
|
|
$courseId = api_get_course_int_id(); |
6086
|
|
|
$preRequisiteId = $lpItem->getPrerequisite(); |
6087
|
|
|
$itemId = $lpItem->getIid(); |
6088
|
|
|
|
6089
|
|
|
$return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle()); |
6090
|
|
|
|
6091
|
|
|
$return .= '<form method="POST">'; |
6092
|
|
|
$return .= '<div class="table-responsive">'; |
6093
|
|
|
$return .= '<table class="table table-hover">'; |
6094
|
|
|
$return .= '<thead>'; |
6095
|
|
|
$return .= '<tr>'; |
6096
|
|
|
$return .= '<th>'.get_lang('Prerequisites').'</th>'; |
6097
|
|
|
$return .= '<th width="140">'.get_lang('minimum').'</th>'; |
6098
|
|
|
$return .= '<th width="140">'.get_lang('maximum').'</th>'; |
6099
|
|
|
$return .= '</tr>'; |
6100
|
|
|
$return .= '</thead>'; |
6101
|
|
|
|
6102
|
|
|
// Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146 |
6103
|
|
|
$return .= '<tbody>'; |
6104
|
|
|
$return .= '<tr>'; |
6105
|
|
|
$return .= '<td colspan="3">'; |
6106
|
|
|
$return .= '<div class="radio learnpath"><label for="idnone">'; |
6107
|
|
|
$return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />'; |
6108
|
|
|
$return .= get_lang('none').'</label>'; |
6109
|
|
|
$return .= '</div>'; |
6110
|
|
|
$return .= '</tr>'; |
6111
|
|
|
|
6112
|
|
|
// @todo use entitites |
6113
|
|
|
$tblLpItem = Database::get_course_table(TABLE_LP_ITEM); |
6114
|
|
|
$sql = "SELECT * FROM $tblLpItem |
6115
|
|
|
WHERE lp_id = ".$this->lp_id; |
6116
|
|
|
$result = Database::query($sql); |
6117
|
|
|
|
6118
|
|
|
$selectedMinScore = []; |
6119
|
|
|
$selectedMaxScore = []; |
6120
|
|
|
$masteryScore = []; |
6121
|
|
|
while ($row = Database::fetch_array($result)) { |
6122
|
|
|
if ($row['iid'] == $itemId) { |
6123
|
|
|
$selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score']; |
6124
|
|
|
$selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score']; |
6125
|
|
|
} |
6126
|
|
|
$masteryScore[$row['iid']] = $row['mastery_score']; |
6127
|
|
|
} |
6128
|
|
|
|
6129
|
|
|
$displayOrder = $lpItem->getDisplayOrder(); |
6130
|
|
|
$lpItemRepo = Container::getLpItemRepository(); |
6131
|
|
|
$itemRoot = $lpItemRepo->getRootItem($this->get_id()); |
6132
|
|
|
$em = Database::getManager(); |
6133
|
|
|
|
6134
|
|
|
$currentItemId = $itemId; |
6135
|
|
|
$options = [ |
6136
|
|
|
'decorate' => true, |
6137
|
|
|
'rootOpen' => function () { |
6138
|
|
|
return ''; |
6139
|
|
|
}, |
6140
|
|
|
'rootClose' => function () { |
6141
|
|
|
return ''; |
6142
|
|
|
}, |
6143
|
|
|
'childOpen' => function () { |
6144
|
|
|
return ''; |
6145
|
|
|
}, |
6146
|
|
|
'childClose' => '', |
6147
|
|
|
'nodeDecorator' => function ($item) use ( |
6148
|
|
|
$currentItemId, |
6149
|
|
|
$preRequisiteId, |
6150
|
|
|
$courseId, |
6151
|
|
|
$selectedMaxScore, |
6152
|
|
|
$selectedMinScore, |
6153
|
|
|
$displayOrder, |
6154
|
|
|
$lpItemRepo, |
6155
|
|
|
$em |
6156
|
|
|
) { |
6157
|
|
|
$itemId = $item['iid']; |
6158
|
|
|
$type = $item['itemType']; |
6159
|
|
|
$iconName = str_replace(' ', '', $type); |
6160
|
|
|
switch ($iconName) { |
6161
|
|
|
case 'category': |
6162
|
|
|
case 'chapter': |
6163
|
|
|
case 'folder': |
6164
|
|
|
case 'dir': |
6165
|
|
|
$icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY); |
6166
|
|
|
break; |
6167
|
|
|
default: |
6168
|
|
|
$icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY); |
6169
|
|
|
break; |
6170
|
|
|
} |
6171
|
|
|
|
6172
|
|
|
if ($itemId == $currentItemId) { |
6173
|
|
|
return ''; |
6174
|
|
|
} |
6175
|
|
|
|
6176
|
|
|
if ($displayOrder < $item['displayOrder']) { |
6177
|
|
|
return ''; |
6178
|
|
|
} |
6179
|
|
|
|
6180
|
|
|
$selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore']; |
6181
|
|
|
$selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0; |
6182
|
|
|
$masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0; |
6183
|
|
|
|
6184
|
|
|
$return = '<tr>'; |
6185
|
|
|
$return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>'; |
6186
|
|
|
$return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">'; |
6187
|
|
|
$return .= '<label for="id'.$itemId.'">'; |
6188
|
|
|
|
6189
|
|
|
$checked = ''; |
6190
|
|
|
if (null !== $preRequisiteId) { |
6191
|
|
|
$checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : ''; |
6192
|
|
|
} |
6193
|
|
|
|
6194
|
|
|
$disabled = 'dir' === $type ? ' disabled="disabled" ' : ''; |
6195
|
|
|
|
6196
|
|
|
$return .= '<input |
6197
|
|
|
'.$checked.' '.$disabled.' |
6198
|
|
|
id="id'.$itemId.'" |
6199
|
|
|
name="prerequisites" |
6200
|
|
|
type="radio" |
6201
|
|
|
value="'.$itemId.'" />'; |
6202
|
|
|
|
6203
|
|
|
$return .= $icon.' '.$item['title'].'</label>'; |
6204
|
|
|
$return .= '</div>'; |
6205
|
|
|
$return .= '</td>'; |
6206
|
|
|
|
6207
|
|
|
if (TOOL_QUIZ == $type) { |
6208
|
|
|
// let's update max_score Tests information depending of the Tests Advanced properties |
6209
|
|
|
$exercise = new Exercise($courseId); |
6210
|
|
|
/** @var CLpItem $itemEntity */ |
6211
|
|
|
$itemEntity = $lpItemRepo->find($itemId); |
6212
|
|
|
$exercise->read($item['path']); |
6213
|
|
|
$itemEntity->setMaxScore($exercise->getMaxScore()); |
6214
|
|
|
$em->persist($itemEntity); |
6215
|
|
|
$em->flush($itemEntity); |
6216
|
|
|
|
6217
|
|
|
$item['maxScore'] = $exercise->getMaxScore(); |
6218
|
|
|
|
6219
|
|
|
if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) { |
6220
|
|
|
// Backwards compatibility with 1.9.x use mastery_score as min value |
6221
|
|
|
$selectedMinScoreValue = $masteryScoreAsMinValue; |
6222
|
|
|
} |
6223
|
|
|
$return .= '<td>'; |
6224
|
|
|
$return .= '<input |
6225
|
|
|
class="form-control" |
6226
|
|
|
size="4" maxlength="3" |
6227
|
|
|
name="min_'.$itemId.'" |
6228
|
|
|
type="number" |
6229
|
|
|
min="0" |
6230
|
|
|
step="any" |
6231
|
|
|
max="'.$item['maxScore'].'" |
6232
|
|
|
value="'.$selectedMinScoreValue.'" |
6233
|
|
|
/>'; |
6234
|
|
|
$return .= '</td>'; |
6235
|
|
|
$return .= '<td>'; |
6236
|
|
|
$return .= '<input |
6237
|
|
|
class="form-control" |
6238
|
|
|
size="4" |
6239
|
|
|
maxlength="3" |
6240
|
|
|
name="max_'.$itemId.'" |
6241
|
|
|
type="number" |
6242
|
|
|
min="0" |
6243
|
|
|
step="any" |
6244
|
|
|
max="'.$item['maxScore'].'" |
6245
|
|
|
value="'.$selectedMaxScoreValue.'" |
6246
|
|
|
/>'; |
6247
|
|
|
$return .= '</td>'; |
6248
|
|
|
} |
6249
|
|
|
|
6250
|
|
|
if (TOOL_HOTPOTATOES == $type) { |
6251
|
|
|
$return .= '<td>'; |
6252
|
|
|
$return .= '<input |
6253
|
|
|
size="4" |
6254
|
|
|
maxlength="3" |
6255
|
|
|
name="min_'.$itemId.'" |
6256
|
|
|
type="number" |
6257
|
|
|
min="0" |
6258
|
|
|
step="any" |
6259
|
|
|
max="'.$item['maxScore'].'" |
6260
|
|
|
value="'.$selectedMinScoreValue.'" |
6261
|
|
|
/>'; |
6262
|
|
|
$return .= '</td>'; |
6263
|
|
|
$return .= '<td>'; |
6264
|
|
|
$return .= '<input |
6265
|
|
|
size="4" |
6266
|
|
|
maxlength="3" |
6267
|
|
|
name="max_'.$itemId.'" |
6268
|
|
|
type="number" |
6269
|
|
|
min="0" |
6270
|
|
|
step="any" |
6271
|
|
|
max="'.$item['maxScore'].'" |
6272
|
|
|
value="'.$selectedMaxScoreValue.'" |
6273
|
|
|
/>'; |
6274
|
|
|
$return .= '</td>'; |
6275
|
|
|
} |
6276
|
|
|
$return .= '</tr>'; |
6277
|
|
|
|
6278
|
|
|
return $return; |
6279
|
|
|
}, |
6280
|
|
|
]; |
6281
|
|
|
|
6282
|
|
|
$tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options); |
6283
|
|
|
$return .= $tree; |
6284
|
|
|
$return .= '</tbody>'; |
6285
|
|
|
$return .= '</table>'; |
6286
|
|
|
$return .= '</div>'; |
6287
|
|
|
$return .= '<div class="form-group">'; |
6288
|
|
|
$return .= '<button class="btn btn--primary" name="submit_button" type="submit">'. |
6289
|
|
|
get_lang('Save prerequisites settings').'</button>'; |
6290
|
|
|
$return .= '</form>'; |
6291
|
|
|
|
6292
|
|
|
return $return; |
6293
|
|
|
} |
6294
|
|
|
|
6295
|
|
|
/** |
6296
|
|
|
* Return HTML list to allow prerequisites selection for lp. |
6297
|
|
|
*/ |
6298
|
|
|
public function display_lp_prerequisites_list(FormValidator $form) |
6299
|
|
|
{ |
6300
|
|
|
$lp_id = $this->lp_id; |
6301
|
|
|
$lp = api_get_lp_entity($lp_id); |
6302
|
|
|
$prerequisiteId = $lp->getPrerequisite(); |
6303
|
|
|
|
6304
|
|
|
$repo = Container::getLpRepository(); |
6305
|
|
|
$qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity()); |
6306
|
|
|
/** @var CLp[] $lps */ |
6307
|
|
|
$lps = $qb->getQuery()->getResult(); |
6308
|
|
|
|
6309
|
|
|
//$session_id = api_get_session_id(); |
6310
|
|
|
/*$session_condition = api_get_session_condition($session_id, true, true); |
6311
|
|
|
$sql = "SELECT * FROM $tbl_lp |
6312
|
|
|
WHERE c_id = $course_id $session_condition |
6313
|
|
|
ORDER BY display_order "; |
6314
|
|
|
$rs = Database::query($sql);*/ |
6315
|
|
|
|
6316
|
|
|
$items = [get_lang('none')]; |
6317
|
|
|
foreach ($lps as $lp) { |
6318
|
|
|
$myLpId = $lp->getIid(); |
6319
|
|
|
if ($myLpId == $lp_id) { |
6320
|
|
|
continue; |
6321
|
|
|
} |
6322
|
|
|
$items[$myLpId] = $lp->getTitle(); |
6323
|
|
|
/*$return .= '<option |
6324
|
|
|
value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'. |
6325
|
|
|
$lp->getName(). |
6326
|
|
|
'</option>';*/ |
6327
|
|
|
} |
6328
|
|
|
|
6329
|
|
|
$select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items); |
6330
|
|
|
$select->setSelected($prerequisiteId); |
6331
|
|
|
} |
6332
|
|
|
|
6333
|
|
|
/** |
6334
|
|
|
* Creates a list with all the documents in it. |
6335
|
|
|
* |
6336
|
|
|
* @param bool $showInvisibleFiles |
6337
|
|
|
* |
6338
|
|
|
* @throws Exception |
6339
|
|
|
* |
6340
|
|
|
* |
6341
|
|
|
* @return string |
6342
|
|
|
*/ |
6343
|
|
|
public function get_documents($showInvisibleFiles = false) |
6344
|
|
|
{ |
6345
|
|
|
$sessionId = api_get_session_id(); |
6346
|
|
|
$documentTree = DocumentManager::get_document_preview( |
6347
|
|
|
api_get_course_entity(), |
6348
|
|
|
$this->lp_id, |
6349
|
|
|
null, |
6350
|
|
|
$sessionId, |
6351
|
|
|
true, |
6352
|
|
|
null, |
6353
|
|
|
null, |
6354
|
|
|
$showInvisibleFiles, |
6355
|
|
|
true |
6356
|
|
|
); |
6357
|
|
|
|
6358
|
|
|
$form = new FormValidator( |
6359
|
|
|
'form_upload', |
6360
|
|
|
'POST', |
6361
|
|
|
$this->getCurrentBuildingModeURL(), |
6362
|
|
|
'', |
6363
|
|
|
['enctype' => 'multipart/form-data'] |
6364
|
|
|
); |
6365
|
|
|
|
6366
|
|
|
$folders = DocumentManager::get_all_document_folders( |
6367
|
|
|
api_get_course_info(), |
6368
|
|
|
0, |
6369
|
|
|
true |
6370
|
|
|
); |
6371
|
|
|
|
6372
|
|
|
$folder = $this->generate_lp_folder(api_get_course_info()); |
6373
|
|
|
|
6374
|
|
|
DocumentManager::build_directory_selector( |
6375
|
|
|
$folders, |
6376
|
|
|
$folder->getIid(), |
6377
|
|
|
[], |
6378
|
|
|
true, |
6379
|
|
|
$form, |
6380
|
|
|
'directory_parent_id' |
6381
|
|
|
); |
6382
|
|
|
|
6383
|
|
|
$group = [ |
6384
|
|
|
$form->createElement( |
6385
|
|
|
'radio', |
6386
|
|
|
'if_exists', |
6387
|
|
|
get_lang('If file exists:'), |
6388
|
|
|
get_lang('Do nothing'), |
6389
|
|
|
'nothing' |
6390
|
|
|
), |
6391
|
|
|
$form->createElement( |
6392
|
|
|
'radio', |
6393
|
|
|
'if_exists', |
6394
|
|
|
null, |
6395
|
|
|
get_lang('Overwrite the existing file'), |
6396
|
|
|
'overwrite' |
6397
|
|
|
), |
6398
|
|
|
$form->createElement( |
6399
|
|
|
'radio', |
6400
|
|
|
'if_exists', |
6401
|
|
|
null, |
6402
|
|
|
get_lang('Rename the uploaded file if it exists'), |
6403
|
|
|
'rename' |
6404
|
|
|
), |
6405
|
|
|
]; |
6406
|
|
|
$form->addGroup($group, null, get_lang('If file exists:')); |
6407
|
|
|
|
6408
|
|
|
$fileExistsOption = api_get_setting('document.document_if_file_exists_option'); |
6409
|
|
|
$defaultFileExistsOption = 'rename'; |
6410
|
|
|
if (!empty($fileExistsOption)) { |
6411
|
|
|
$defaultFileExistsOption = $fileExistsOption; |
6412
|
|
|
} |
6413
|
|
|
$form->setDefaults(['if_exists' => $defaultFileExistsOption]); |
6414
|
|
|
|
6415
|
|
|
// Check box options |
6416
|
|
|
$form->addCheckBox( |
6417
|
|
|
'unzip', |
6418
|
|
|
get_lang('Options'), |
6419
|
|
|
get_lang('Uncompress zip') |
6420
|
|
|
); |
6421
|
|
|
|
6422
|
|
|
$url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath='; |
6423
|
|
|
$form->addMultipleUpload($url); |
6424
|
|
|
|
6425
|
|
|
$lpItem = (new CLpItem()) |
6426
|
|
|
->setTitle('') |
6427
|
|
|
->setItemType(TOOL_DOCUMENT) |
6428
|
|
|
; |
6429
|
|
|
$new = $this->displayDocumentForm('add', $lpItem); |
6430
|
|
|
|
6431
|
|
|
/*$lpItem = new CLpItem(); |
6432
|
|
|
$lpItem->setItemType(TOOL_READOUT_TEXT); |
6433
|
|
|
$frmReadOutText = $this->displayDocumentForm('add');*/ |
6434
|
|
|
|
6435
|
|
|
$headers = [ |
6436
|
|
|
get_lang('Files'), |
6437
|
|
|
get_lang('Create a new document'), |
6438
|
|
|
//get_lang('Create read-out text'), |
6439
|
|
|
get_lang('Upload'), |
6440
|
|
|
]; |
6441
|
|
|
|
6442
|
|
|
return Display::tabs( |
6443
|
|
|
$headers, |
6444
|
|
|
[$documentTree, $new, $form->returnForm()], |
6445
|
|
|
'subtab', |
6446
|
|
|
['class' => 'mt-2'] |
6447
|
|
|
); |
6448
|
|
|
} |
6449
|
|
|
|
6450
|
|
|
/** |
6451
|
|
|
* Creates a list with all the exercises (quiz) in it. |
6452
|
|
|
* |
6453
|
|
|
* @return string |
6454
|
|
|
*/ |
6455
|
|
|
public function get_exercises() |
6456
|
|
|
{ |
6457
|
|
|
$course_id = api_get_course_int_id(); |
6458
|
|
|
$session_id = api_get_session_id(); |
6459
|
|
|
$setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc'); |
6460
|
|
|
|
6461
|
|
|
//$activeCondition = ' active <> -1 '; |
6462
|
|
|
$active = 2; |
6463
|
|
|
if ($setting) { |
6464
|
|
|
$active = 1; |
6465
|
|
|
//$activeCondition = ' active = 1 '; |
6466
|
|
|
} |
6467
|
|
|
|
6468
|
|
|
$categoryCondition = ''; |
6469
|
|
|
|
6470
|
|
|
$keyword = $_REQUEST['keyword'] ?? null; |
6471
|
|
|
$categoryId = $_REQUEST['category_id'] ?? null; |
6472
|
|
|
/*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) { |
6473
|
|
|
$categoryCondition = " AND exercise_category_id = $categoryId "; |
6474
|
|
|
} |
6475
|
|
|
|
6476
|
|
|
$keywordCondition = ''; |
6477
|
|
|
|
6478
|
|
|
if (!empty($keyword)) { |
6479
|
|
|
$keyword = Database::escape_string($keyword); |
6480
|
|
|
$keywordCondition = " AND title LIKE '%$keyword%' "; |
6481
|
|
|
} |
6482
|
|
|
*/ |
6483
|
|
|
$course = api_get_course_entity($course_id); |
6484
|
|
|
$session = api_get_session_entity($session_id); |
6485
|
|
|
|
6486
|
|
|
$qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId); |
6487
|
|
|
/** @var CQuiz[] $exercises */ |
6488
|
|
|
$exercises = $qb->getQuery()->getResult(); |
6489
|
|
|
|
6490
|
|
|
/*$sql_quiz = "SELECT * FROM $tbl_quiz |
6491
|
|
|
WHERE |
6492
|
|
|
c_id = $course_id AND |
6493
|
|
|
$activeCondition |
6494
|
|
|
$condition_session |
6495
|
|
|
$categoryCondition |
6496
|
|
|
$keywordCondition |
6497
|
|
|
ORDER BY title ASC"; |
6498
|
|
|
$res_quiz = Database::query($sql_quiz);*/ |
6499
|
|
|
|
6500
|
|
|
$currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2'; |
6501
|
|
|
|
6502
|
|
|
// Create a search-box |
6503
|
|
|
/*$form = new FormValidator('search_simple', 'get', $currentUrl); |
6504
|
|
|
$form->addHidden('action', 'add_item'); |
6505
|
|
|
$form->addHidden('type', 'step'); |
6506
|
|
|
$form->addHidden('lp_id', $this->lp_id); |
6507
|
|
|
$form->addHidden('lp_build_selected', '2'); |
6508
|
|
|
|
6509
|
|
|
$form->addCourseHiddenParams(); |
6510
|
|
|
$form->addText( |
6511
|
|
|
'keyword', |
6512
|
|
|
get_lang('Search'), |
6513
|
|
|
false, |
6514
|
|
|
[ |
6515
|
|
|
'aria-label' => get_lang('Search'), |
6516
|
|
|
] |
6517
|
|
|
); |
6518
|
|
|
|
6519
|
|
|
if (api_get_configuration_value('allow_exercise_categories')) { |
6520
|
|
|
$manager = new ExerciseCategoryManager(); |
6521
|
|
|
$options = $manager->getCategoriesForSelect(api_get_course_int_id()); |
6522
|
|
|
if (!empty($options)) { |
6523
|
|
|
$form->addSelect( |
6524
|
|
|
'category_id', |
6525
|
|
|
get_lang('Category'), |
6526
|
|
|
$options, |
6527
|
|
|
['placeholder' => get_lang('Please select an option')] |
6528
|
|
|
); |
6529
|
|
|
} |
6530
|
|
|
} |
6531
|
|
|
|
6532
|
|
|
$form->addButtonSearch(get_lang('Search')); |
6533
|
|
|
$return = $form->returnForm();*/ |
6534
|
|
|
|
6535
|
|
|
$return = '<ul class="mt-2 bg-white list-group lp_resource">'; |
6536
|
|
|
$return .= '<li class="list-group-item lp_resource_element disable_drag">'; |
6537
|
|
|
$return .= Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 32, get_lang('New test')); |
6538
|
|
|
$return .= '<a |
6539
|
|
|
href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'. |
6540
|
|
|
get_lang('New test').'</a>'; |
6541
|
|
|
$return .= '</li>'; |
6542
|
|
|
|
6543
|
|
|
$previewIcon = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')); |
6544
|
|
|
$quizIcon = Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 16, get_lang('Exercise')); |
6545
|
|
|
$moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
6546
|
|
|
$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq(); |
6547
|
|
|
foreach ($exercises as $exercise) { |
6548
|
|
|
$exerciseId = $exercise->getIid(); |
6549
|
|
|
$title = strip_tags(api_html_entity_decode($exercise->getTitle())); |
6550
|
|
|
$visibility = $exercise->isVisible($course, $session); |
6551
|
|
|
|
6552
|
|
|
$link = Display::url( |
6553
|
|
|
$previewIcon, |
6554
|
|
|
$exerciseUrl.'&exerciseId='.$exerciseId, |
6555
|
|
|
['target' => '_blank'] |
6556
|
|
|
); |
6557
|
|
|
$return .= '<li |
6558
|
|
|
class="list-group-item lp_resource_element" |
6559
|
|
|
id="'.$exerciseId.'" |
6560
|
|
|
data-id="'.$exerciseId.'" |
6561
|
|
|
title="'.$title.'">'; |
6562
|
|
|
$return .= Display::url($moveIcon, '#', ['class' => 'moved']); |
6563
|
|
|
$return .= $quizIcon; |
6564
|
|
|
$sessionStar = ''; |
6565
|
|
|
/*$sessionStar = api_get_session_image( |
6566
|
|
|
$row_quiz['session_id'], |
6567
|
|
|
$userInfo['status'] |
6568
|
|
|
);*/ |
6569
|
|
|
$return .= Display::url( |
6570
|
|
|
Security::remove_XSS(cut($title, 80)).$link.$sessionStar, |
6571
|
|
|
api_get_self().'?'. |
6572
|
|
|
api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id, |
6573
|
|
|
[ |
6574
|
|
|
'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id', |
6575
|
|
|
'data_type' => 'quiz', |
6576
|
|
|
'data-id' => $exerciseId, |
6577
|
|
|
] |
6578
|
|
|
); |
6579
|
|
|
$return .= '</li>'; |
6580
|
|
|
} |
6581
|
|
|
|
6582
|
|
|
$return .= '</ul>'; |
6583
|
|
|
|
6584
|
|
|
return $return; |
6585
|
|
|
} |
6586
|
|
|
|
6587
|
|
|
/** |
6588
|
|
|
* Creates a list with all the links in it. |
6589
|
|
|
* |
6590
|
|
|
* @return string |
6591
|
|
|
*/ |
6592
|
|
|
public function get_links() |
6593
|
|
|
{ |
6594
|
|
|
$sessionId = api_get_session_id(); |
6595
|
|
|
$repo = Container::getLinkRepository(); |
6596
|
|
|
|
6597
|
|
|
$course = api_get_course_entity(); |
6598
|
|
|
$session = api_get_session_entity($sessionId); |
6599
|
|
|
$qb = $repo->getResourcesByCourse($course, $session); |
6600
|
|
|
/** @var CLink[] $links */ |
6601
|
|
|
$links = $qb->getQuery()->getResult(); |
6602
|
|
|
|
6603
|
|
|
$selfUrl = api_get_self(); |
6604
|
|
|
$courseIdReq = api_get_cidreq(); |
6605
|
|
|
$userInfo = api_get_user_info(); |
6606
|
|
|
|
6607
|
|
|
$moveEverywhereIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
6608
|
|
|
|
6609
|
|
|
$categorizedLinks = []; |
6610
|
|
|
$categories = []; |
6611
|
|
|
|
6612
|
|
|
foreach ($links as $link) { |
6613
|
|
|
$categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0; |
6614
|
|
|
if (empty($categoryId)) { |
6615
|
|
|
$categories[0] = get_lang('Uncategorized'); |
6616
|
|
|
} else { |
6617
|
|
|
$category = $link->getCategory(); |
6618
|
|
|
$categories[$categoryId] = $category->getTitle(); |
6619
|
|
|
} |
6620
|
|
|
$categorizedLinks[$categoryId][$link->getIid()] = $link; |
6621
|
|
|
} |
6622
|
|
|
|
6623
|
|
|
$linksHtmlCode = |
6624
|
|
|
'<script> |
6625
|
|
|
function toggle_tool(tool, id) { |
6626
|
|
|
if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){ |
6627
|
|
|
document.getElementById(tool+"_"+id+"_content").style.display = "block"; |
6628
|
|
|
document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'"; |
6629
|
|
|
} else { |
6630
|
|
|
document.getElementById(tool+"_"+id+"_content").style.display = "none"; |
6631
|
|
|
document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'"; |
6632
|
|
|
} |
6633
|
|
|
} |
6634
|
|
|
</script> |
6635
|
|
|
|
6636
|
|
|
<ul class="mt-2 bg-white list-group lp_resource"> |
6637
|
|
|
<li class="list-group-item lp_resource_element disable_drag "> |
6638
|
|
|
'.Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL).' |
6639
|
|
|
<a |
6640
|
|
|
href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" |
6641
|
|
|
title="'.get_lang('Add a link').'">'. |
6642
|
|
|
get_lang('Add a link').' |
6643
|
|
|
</a> |
6644
|
|
|
</li>'; |
6645
|
|
|
$linkIcon = Display::getMdiIcon('file-link', 'ch-tool-icon', null, 16, get_lang('Link')); |
6646
|
|
|
foreach ($categorizedLinks as $categoryId => $links) { |
6647
|
|
|
$linkNodes = null; |
6648
|
|
|
/** @var CLink $link */ |
6649
|
|
|
foreach ($links as $key => $link) { |
6650
|
|
|
$title = $link->getTitle(); |
6651
|
|
|
$id = $link->getIid(); |
6652
|
|
|
$linkUrl = Display::url( |
6653
|
|
|
Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')), |
6654
|
|
|
api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key, |
6655
|
|
|
['target' => '_blank'] |
6656
|
|
|
); |
6657
|
|
|
|
6658
|
|
|
if ($link->isVisible($course, $session)) { |
6659
|
|
|
//$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']); |
6660
|
|
|
$sessionStar = ''; |
6661
|
|
|
$url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id; |
6662
|
|
|
$link = Display::url( |
6663
|
|
|
Security::remove_XSS($title).$sessionStar.$linkUrl, |
6664
|
|
|
$url, |
6665
|
|
|
[ |
6666
|
|
|
'class' => 'moved link_with_id', |
6667
|
|
|
'data-id' => $key, |
6668
|
|
|
'data_type' => TOOL_LINK, |
6669
|
|
|
'title' => $title, |
6670
|
|
|
] |
6671
|
|
|
); |
6672
|
|
|
$linkNodes .= |
6673
|
|
|
"<li |
6674
|
|
|
class='list-group-item lp_resource_element' |
6675
|
|
|
id= $id |
6676
|
|
|
data-id= $id |
6677
|
|
|
> |
6678
|
|
|
<a class='moved' href='#'> |
6679
|
|
|
$moveEverywhereIcon |
6680
|
|
|
</a> |
6681
|
|
|
$linkIcon $link |
6682
|
|
|
</li>"; |
6683
|
|
|
} |
6684
|
|
|
} |
6685
|
|
|
$linksHtmlCode .= |
6686
|
|
|
'<li class="list-group-item disable_drag"> |
6687
|
|
|
<a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" > |
6688
|
|
|
<img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener" |
6689
|
|
|
align="absbottom" /> |
6690
|
|
|
</a> |
6691
|
|
|
<span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span> |
6692
|
|
|
</li> |
6693
|
|
|
'. |
6694
|
|
|
$linkNodes. |
6695
|
|
|
''; |
6696
|
|
|
//<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'. |
6697
|
|
|
} |
6698
|
|
|
$linksHtmlCode .= '</ul>'; |
6699
|
|
|
|
6700
|
|
|
return $linksHtmlCode; |
6701
|
|
|
} |
6702
|
|
|
|
6703
|
|
|
/** |
6704
|
|
|
* Creates a list with all the student publications in it. |
6705
|
|
|
* |
6706
|
|
|
* @return string |
6707
|
|
|
*/ |
6708
|
|
|
public function get_student_publications() |
6709
|
|
|
{ |
6710
|
|
|
$return = '<ul class="mt-2 bg-white list-group lp_resource">'; |
6711
|
|
|
$return .= '<li class="list-group-item lp_resource_element">'; |
6712
|
|
|
$works = getWorkListTeacher(0, 100, null, null, null); |
6713
|
|
|
if (!empty($works)) { |
6714
|
|
|
$icon = Display::getMdiIcon('inbox-full', 'ch-tool-icon',null, 16, get_lang('Student publication')); |
6715
|
|
|
foreach ($works as $work) { |
6716
|
|
|
$workId = $work['iid']; |
6717
|
|
|
$link = Display::url( |
6718
|
|
|
Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')), |
6719
|
|
|
api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId, |
6720
|
|
|
['target' => '_blank'] |
6721
|
|
|
); |
6722
|
|
|
|
6723
|
|
|
$return .= '<li |
6724
|
|
|
class="list-group-item lp_resource_element" |
6725
|
|
|
id="'.$workId.'" |
6726
|
|
|
data-id="'.$workId.'" |
6727
|
|
|
>'; |
6728
|
|
|
$return .= '<a class="moved" href="#">'; |
6729
|
|
|
$return .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
6730
|
|
|
$return .= '</a> '; |
6731
|
|
|
|
6732
|
|
|
$return .= $icon; |
6733
|
|
|
$return .= Display::url( |
6734
|
|
|
Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link, |
6735
|
|
|
api_get_self().'?'. |
6736
|
|
|
api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id, |
6737
|
|
|
[ |
6738
|
|
|
'class' => 'moved link_with_id', |
6739
|
|
|
'data-id' => $work['iid'], |
6740
|
|
|
'data_type' => TOOL_STUDENTPUBLICATION, |
6741
|
|
|
'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)), |
6742
|
|
|
] |
6743
|
|
|
); |
6744
|
|
|
$return .= '</li>'; |
6745
|
|
|
} |
6746
|
|
|
} |
6747
|
|
|
|
6748
|
|
|
$return .= '</ul>'; |
6749
|
|
|
|
6750
|
|
|
return $return; |
6751
|
|
|
} |
6752
|
|
|
|
6753
|
|
|
/** |
6754
|
|
|
* Creates a list with all the forums in it. |
6755
|
|
|
* |
6756
|
|
|
* @return string |
6757
|
|
|
*/ |
6758
|
|
|
public function get_forums() |
6759
|
|
|
{ |
6760
|
|
|
$forumCategories = get_forum_categories(); |
6761
|
|
|
$forumsInNoCategory = get_forums_in_category(0); |
6762
|
|
|
if (!empty($forumsInNoCategory)) { |
6763
|
|
|
$forumCategories = array_merge( |
6764
|
|
|
$forumCategories, |
6765
|
|
|
[ |
6766
|
|
|
[ |
6767
|
|
|
'cat_id' => 0, |
6768
|
|
|
'session_id' => 0, |
6769
|
|
|
'visibility' => 1, |
6770
|
|
|
'cat_comment' => null, |
6771
|
|
|
], |
6772
|
|
|
] |
6773
|
|
|
); |
6774
|
|
|
} |
6775
|
|
|
|
6776
|
|
|
$a_forums = []; |
6777
|
|
|
$courseEntity = api_get_course_entity(api_get_course_int_id()); |
6778
|
|
|
$sessionEntity = api_get_session_entity(api_get_session_id()); |
6779
|
|
|
|
6780
|
|
|
foreach ($forumCategories as $forumCategory) { |
6781
|
|
|
// The forums in this category. |
6782
|
|
|
$forumsInCategory = get_forums_in_category($forumCategory->getIid()); |
6783
|
|
|
if (!empty($forumsInCategory)) { |
6784
|
|
|
foreach ($forumsInCategory as $forum) { |
6785
|
|
|
if ($forum->isVisible($courseEntity, $sessionEntity)) { |
6786
|
|
|
$a_forums[] = $forum; |
6787
|
|
|
} |
6788
|
|
|
} |
6789
|
|
|
} |
6790
|
|
|
} |
6791
|
|
|
|
6792
|
|
|
$return = '<ul class="mt-2 bg-white list-group lp_resource">'; |
6793
|
|
|
|
6794
|
|
|
// First add link |
6795
|
|
|
$return .= '<li class="list-group-item lp_resource_element disable_drag">'; |
6796
|
|
|
$return .= Display::getMdiIcon('comment-quote ', 'ch-tool-icon', null, 32, get_lang('Create a new forum')); |
6797
|
|
|
$return .= Display::url( |
6798
|
|
|
get_lang('Create a new forum'), |
6799
|
|
|
api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([ |
6800
|
|
|
'action' => 'add', |
6801
|
|
|
'content' => 'forum', |
6802
|
|
|
'lp_id' => $this->lp_id, |
6803
|
|
|
]), |
6804
|
|
|
['title' => get_lang('Create a new forum')] |
6805
|
|
|
); |
6806
|
|
|
$return .= '</li>'; |
6807
|
|
|
|
6808
|
|
|
$return .= '<script> |
6809
|
|
|
function toggle_forum(forum_id) { |
6810
|
|
|
if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") { |
6811
|
|
|
document.getElementById("forum_"+forum_id+"_content").style.display = "block"; |
6812
|
|
|
document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'"; |
6813
|
|
|
} else { |
6814
|
|
|
document.getElementById("forum_"+forum_id+"_content").style.display = "none"; |
6815
|
|
|
document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'"; |
6816
|
|
|
} |
6817
|
|
|
} |
6818
|
|
|
</script>'; |
6819
|
|
|
$moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
6820
|
|
|
$userRights = api_is_allowed_to_edit(false, true); |
6821
|
|
|
foreach ($a_forums as $forum) { |
6822
|
|
|
$forumSession = $forum->getFirstResourceLink()->getSession(); |
6823
|
|
|
$isForumSession = (null !== $forumSession); |
6824
|
|
|
$forumId = $forum->getIid(); |
6825
|
|
|
$title = Security::remove_XSS($forum->getTitle()); |
6826
|
|
|
$link = Display::url( |
6827
|
|
|
Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')), |
6828
|
|
|
api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId, |
6829
|
|
|
['target' => '_blank'] |
6830
|
|
|
); |
6831
|
|
|
|
6832
|
|
|
$return .= '<li |
6833
|
|
|
class="list-group-item lp_resource_element" |
6834
|
|
|
id="'.$forumId.'" |
6835
|
|
|
data-id="'.$forumId.'" |
6836
|
|
|
>'; |
6837
|
|
|
$return .= '<a class="moved" href="#">'; |
6838
|
|
|
$return .= $moveIcon; |
6839
|
|
|
$return .= ' </a>'; |
6840
|
|
|
$return .= Display::getMdiIcon('comment-quote', 'ch-tool-icon', null, 16, get_lang('Forum')); |
6841
|
|
|
|
6842
|
|
|
$moveLink = Display::url( |
6843
|
|
|
$title, |
6844
|
|
|
api_get_self().'?'. |
6845
|
|
|
api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id, |
6846
|
|
|
[ |
6847
|
|
|
'class' => 'moved link_with_id', |
6848
|
|
|
'data-id' => $forumId, |
6849
|
|
|
'data_type' => TOOL_FORUM, |
6850
|
|
|
'title' => $title, |
6851
|
|
|
'style' => 'vertical-align:middle', |
6852
|
|
|
] |
6853
|
|
|
); |
6854
|
|
|
$return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle"> |
6855
|
|
|
<img |
6856
|
|
|
src="'.Display::returnIconPath('add.png').'" |
6857
|
|
|
id="forum_'.$forumId.'_opener" align="absbottom" |
6858
|
|
|
/> |
6859
|
|
|
</a> |
6860
|
|
|
'.$moveLink; |
6861
|
|
|
$return .= '</li>'; |
6862
|
|
|
|
6863
|
|
|
$return .= '<div style="display:none" id="forum_'.$forumId.'_content">'; |
6864
|
|
|
$threads = get_threads($forumId); |
6865
|
|
|
if (is_array($threads)) { |
6866
|
|
|
foreach ($threads as $thread) { |
6867
|
|
|
$threadId = $thread->getIid(); |
6868
|
|
|
$link = Display::url( |
6869
|
|
|
Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')), |
6870
|
|
|
api_get_path(WEB_CODE_PATH). |
6871
|
|
|
'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId, |
6872
|
|
|
['target' => '_blank'] |
6873
|
|
|
); |
6874
|
|
|
|
6875
|
|
|
$return .= '<li |
6876
|
|
|
class="list-group-item lp_resource_element" |
6877
|
|
|
id="'.$threadId.'" |
6878
|
|
|
data-id="'.$threadId.'" |
6879
|
|
|
>'; |
6880
|
|
|
$return .= ' <a class="moved" href="#">'; |
6881
|
|
|
$return .= $moveIcon; |
6882
|
|
|
$return .= ' </a>'; |
6883
|
|
|
$return .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, 16, get_lang('Thread')); |
6884
|
|
|
$return .= '<a |
6885
|
|
|
class="moved link_with_id" |
6886
|
|
|
data-id="'.$threadId.'" |
6887
|
|
|
data_type="'.TOOL_THREAD.'" |
6888
|
|
|
title="'.$thread->getTitle().'" |
6889
|
|
|
href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'" |
6890
|
|
|
>'. |
6891
|
|
|
Security::remove_XSS($thread->getTitle()).' '.$link.'</a>'; |
6892
|
|
|
$return .= '</li>'; |
6893
|
|
|
} |
6894
|
|
|
} |
6895
|
|
|
$return .= '</div>'; |
6896
|
|
|
} |
6897
|
|
|
$return .= '</ul>'; |
6898
|
|
|
|
6899
|
|
|
return $return; |
6900
|
|
|
} |
6901
|
|
|
|
6902
|
|
|
/** |
6903
|
|
|
* Creates a list with all the surveys in it. |
6904
|
|
|
* |
6905
|
|
|
* @return string |
6906
|
|
|
*/ |
6907
|
|
|
public function getSurveys() |
6908
|
|
|
{ |
6909
|
|
|
$return = '<ul class="mt-2 bg-white list-group lp_resource">'; |
6910
|
|
|
|
6911
|
|
|
// First add link |
6912
|
|
|
$return .= '<li class="list-group-item lp_resource_element disable_drag">'; |
6913
|
|
|
$return .= Display::getMdiIcon('clipboard-question-outline', 'ch-tool-icon', null, 32, get_lang('CreateNewSurvey')); |
6914
|
|
|
$return .= Display::url( |
6915
|
|
|
get_lang('Create a new survey'), |
6916
|
|
|
api_get_path(WEB_CODE_PATH).'survey/create_new_survey.php?'.api_get_cidreq().'&'.http_build_query([ |
6917
|
|
|
'action' => 'add', |
6918
|
|
|
'lp_id' => $this->lp_id, |
6919
|
|
|
]), |
6920
|
|
|
['title' => get_lang('Create a new survey')] |
6921
|
|
|
); |
6922
|
|
|
$return .= '</li>'; |
6923
|
|
|
|
6924
|
|
|
$surveys = SurveyManager::get_surveys(api_get_course_id(), api_get_session_id()); |
6925
|
|
|
$moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move')); |
6926
|
|
|
|
6927
|
|
|
foreach ($surveys as $survey) { |
6928
|
|
|
if (!empty($survey['iid'])) { |
6929
|
|
|
$surveyTitle = strip_tags($survey['title']); |
6930
|
|
|
$return .= '<li class="list-group-item lp_resource_element" id="'.$survey['iid'].'" data-id="'.$survey['iid'].'">'; |
6931
|
|
|
$return .= '<a class="moved" href="#">'; |
6932
|
|
|
$return .= $moveIcon; |
6933
|
|
|
$return .= ' </a>'; |
6934
|
|
|
$return .= Display::getMdiIcon('poll', 'ch-tool-icon', null, 16, get_lang('Survey')); |
6935
|
|
|
$return .= '<a class="moved link_with_id" data-id="'.$survey['iid'].'" data_type="'.TOOL_SURVEY.'" title="'.$surveyTitle.'" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_SURVEY.'&survey_id='.$survey['iid'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.$surveyTitle.'</a>'; |
6936
|
|
|
$return .= '</li>'; |
6937
|
|
|
} |
6938
|
|
|
} |
6939
|
|
|
|
6940
|
|
|
$return .= '</ul>'; |
6941
|
|
|
|
6942
|
|
|
return $return; |
6943
|
|
|
} |
6944
|
|
|
|
6945
|
|
|
/** |
6946
|
|
|
* Temp function to be moved in main_api or the best place around for this. |
6947
|
|
|
* Creates a file path if it doesn't exist. |
6948
|
|
|
* |
6949
|
|
|
* @param string $path |
6950
|
|
|
*/ |
6951
|
|
|
public function create_path($path) |
6952
|
|
|
{ |
6953
|
|
|
$path_bits = explode('/', dirname($path)); |
6954
|
|
|
|
6955
|
|
|
// IS_WINDOWS_OS has been defined in main_api.lib.php |
6956
|
|
|
$path_built = IS_WINDOWS_OS ? '' : '/'; |
6957
|
|
|
foreach ($path_bits as $bit) { |
6958
|
|
|
if (!empty($bit)) { |
6959
|
|
|
$new_path = $path_built.$bit; |
6960
|
|
|
if (is_dir($new_path)) { |
6961
|
|
|
$path_built = $new_path.'/'; |
6962
|
|
|
} else { |
6963
|
|
|
mkdir($new_path, api_get_permissions_for_new_directories()); |
6964
|
|
|
$path_built = $new_path.'/'; |
6965
|
|
|
} |
6966
|
|
|
} |
6967
|
|
|
} |
6968
|
|
|
} |
6969
|
|
|
|
6970
|
|
|
/** |
6971
|
|
|
* @param int $lp_id |
6972
|
|
|
* @param string $status |
6973
|
|
|
*/ |
6974
|
|
|
public function set_autolaunch($lp_id, $status) |
6975
|
|
|
{ |
6976
|
|
|
$status = (int) $status; |
6977
|
|
|
$em = Database::getManager(); |
6978
|
|
|
$repo = Container::getLpRepository(); |
6979
|
|
|
|
6980
|
|
|
$session = api_get_session_entity(); |
6981
|
|
|
$course = api_get_course_entity(); |
6982
|
|
|
|
6983
|
|
|
$qb = $repo->getResourcesByCourse($course, $session); |
6984
|
|
|
$lps = $qb->getQuery()->getResult(); |
6985
|
|
|
|
6986
|
|
|
foreach ($lps as $lp) { |
6987
|
|
|
$lp->setAutoLaunch(0); |
6988
|
|
|
$em->persist($lp); |
6989
|
|
|
} |
6990
|
|
|
|
6991
|
|
|
$em->flush(); |
6992
|
|
|
|
6993
|
|
|
if ($status === 1) { |
6994
|
|
|
$lp = $repo->find($lp_id); |
6995
|
|
|
if ($lp) { |
6996
|
|
|
$lp->setAutolaunch(1); |
6997
|
|
|
$em->persist($lp); |
6998
|
|
|
} |
6999
|
|
|
$em->flush(); |
7000
|
|
|
} |
7001
|
|
|
} |
7002
|
|
|
|
7003
|
|
|
/** |
7004
|
|
|
* Gets previous_item_id for the next element of the lp_item table. |
7005
|
|
|
* |
7006
|
|
|
* @author Isaac flores paz |
7007
|
|
|
* |
7008
|
|
|
* @return int Previous item ID |
7009
|
|
|
*/ |
7010
|
|
|
public function select_previous_item_id() |
7011
|
|
|
{ |
7012
|
|
|
$course_id = api_get_course_int_id(); |
7013
|
|
|
$table_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
7014
|
|
|
|
7015
|
|
|
// Get the max order of the items |
7016
|
|
|
$sql = "SELECT max(display_order) AS display_order FROM $table_lp_item |
7017
|
|
|
WHERE c_id = $course_id AND lp_id = ".$this->lp_id; |
7018
|
|
|
$rs_max_order = Database::query($sql); |
7019
|
|
|
$row_max_order = Database::fetch_object($rs_max_order); |
7020
|
|
|
$max_order = $row_max_order->display_order; |
7021
|
|
|
// Get the previous item ID |
7022
|
|
|
$sql = "SELECT iid as previous FROM $table_lp_item |
7023
|
|
|
WHERE |
7024
|
|
|
c_id = $course_id AND |
7025
|
|
|
lp_id = ".$this->lp_id." AND |
7026
|
|
|
display_order = '$max_order' "; |
7027
|
|
|
$rs_max = Database::query($sql); |
7028
|
|
|
$row_max = Database::fetch_object($rs_max); |
7029
|
|
|
|
7030
|
|
|
// Return the previous item ID |
7031
|
|
|
return $row_max->previous; |
7032
|
|
|
} |
7033
|
|
|
|
7034
|
|
|
/** |
7035
|
|
|
* Copies an LP. |
7036
|
|
|
*/ |
7037
|
|
|
public function copy() |
7038
|
|
|
{ |
7039
|
|
|
// Course builder |
7040
|
|
|
$cb = new CourseBuilder(); |
7041
|
|
|
|
7042
|
|
|
//Setting tools that will be copied |
7043
|
|
|
$cb->set_tools_to_build(['learnpaths']); |
7044
|
|
|
|
7045
|
|
|
//Setting elements that will be copied |
7046
|
|
|
$cb->set_tools_specific_id_list( |
7047
|
|
|
['learnpaths' => [$this->lp_id]] |
7048
|
|
|
); |
7049
|
|
|
|
7050
|
|
|
$course = $cb->build(); |
7051
|
|
|
|
7052
|
|
|
//Course restorer |
7053
|
|
|
$course_restorer = new CourseRestorer($course); |
7054
|
|
|
$course_restorer->set_add_text_in_items(true); |
7055
|
|
|
$course_restorer->set_tool_copy_settings( |
7056
|
|
|
['learnpaths' => ['reset_dates' => true]] |
7057
|
|
|
); |
7058
|
|
|
$course_restorer->restore( |
7059
|
|
|
api_get_course_id(), |
7060
|
|
|
api_get_session_id(), |
7061
|
|
|
false, |
7062
|
|
|
false |
7063
|
|
|
); |
7064
|
|
|
} |
7065
|
|
|
|
7066
|
|
|
/** |
7067
|
|
|
* Verify document size. |
7068
|
|
|
* |
7069
|
|
|
* @param string $s |
7070
|
|
|
* |
7071
|
|
|
* @return bool |
7072
|
|
|
*/ |
7073
|
|
|
public static function verify_document_size($s) |
7074
|
|
|
{ |
7075
|
|
|
$post_max = ini_get('post_max_size'); |
7076
|
|
|
if ('M' == substr($post_max, -1, 1)) { |
7077
|
|
|
$post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024; |
7078
|
|
|
} elseif ('G' == substr($post_max, -1, 1)) { |
7079
|
|
|
$post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024; |
7080
|
|
|
} |
7081
|
|
|
$upl_max = ini_get('upload_max_filesize'); |
7082
|
|
|
if ('M' == substr($upl_max, -1, 1)) { |
7083
|
|
|
$upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024; |
7084
|
|
|
} elseif ('G' == substr($upl_max, -1, 1)) { |
7085
|
|
|
$upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024; |
7086
|
|
|
} |
7087
|
|
|
|
7088
|
|
|
$repo = Container::getDocumentRepository(); |
7089
|
|
|
$documents_total_space = $repo->getTotalSpace(api_get_course_int_id()); |
7090
|
|
|
|
7091
|
|
|
$course_max_space = DocumentManager::get_course_quota(); |
7092
|
|
|
$total_size = filesize($s) + $documents_total_space; |
7093
|
|
|
if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) { |
7094
|
|
|
return true; |
7095
|
|
|
} |
7096
|
|
|
|
7097
|
|
|
return false; |
7098
|
|
|
} |
7099
|
|
|
|
7100
|
|
|
/** |
7101
|
|
|
* Clear LP prerequisites. |
7102
|
|
|
*/ |
7103
|
|
|
public function clearPrerequisites() |
7104
|
|
|
{ |
7105
|
|
|
$course_id = $this->get_course_int_id(); |
7106
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
7107
|
|
|
$lp_id = $this->get_id(); |
7108
|
|
|
// Cleaning prerequisites |
7109
|
|
|
$sql = "UPDATE $tbl_lp_item SET prerequisite = '' |
7110
|
|
|
WHERE lp_id = $lp_id"; |
7111
|
|
|
Database::query($sql); |
7112
|
|
|
|
7113
|
|
|
// Cleaning mastery score for exercises |
7114
|
|
|
$sql = "UPDATE $tbl_lp_item SET mastery_score = '' |
7115
|
|
|
WHERE lp_id = $lp_id AND item_type = 'quiz'"; |
7116
|
|
|
Database::query($sql); |
7117
|
|
|
} |
7118
|
|
|
|
7119
|
|
|
public function set_previous_step_as_prerequisite_for_all_items() |
7120
|
|
|
{ |
7121
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
7122
|
|
|
$course_id = $this->get_course_int_id(); |
7123
|
|
|
$lp_id = $this->get_id(); |
7124
|
|
|
|
7125
|
|
|
if (!empty($this->items)) { |
7126
|
|
|
$previous_item_id = null; |
7127
|
|
|
$previous_item_max = 0; |
7128
|
|
|
$previous_item_type = null; |
7129
|
|
|
$last_item_not_dir = null; |
7130
|
|
|
$last_item_not_dir_type = null; |
7131
|
|
|
$last_item_not_dir_max = null; |
7132
|
|
|
|
7133
|
|
|
foreach ($this->ordered_items as $itemId) { |
7134
|
|
|
$item = $this->getItem($itemId); |
7135
|
|
|
// if there was a previous item... (otherwise jump to set it) |
7136
|
|
|
if (!empty($previous_item_id)) { |
7137
|
|
|
$current_item_id = $item->get_id(); //save current id |
7138
|
|
|
if ('dir' != $item->get_type()) { |
7139
|
|
|
// Current item is not a folder, so it qualifies to get a prerequisites |
7140
|
|
|
if ('quiz' == $last_item_not_dir_type) { |
7141
|
|
|
// if previous is quiz, mark its max score as default score to be achieved |
7142
|
|
|
$sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max' |
7143
|
|
|
WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir"; |
7144
|
|
|
Database::query($sql); |
7145
|
|
|
} |
7146
|
|
|
// now simply update the prerequisite to set it to the last non-chapter item |
7147
|
|
|
$sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir' |
7148
|
|
|
WHERE lp_id = $lp_id AND iid = $current_item_id"; |
7149
|
|
|
Database::query($sql); |
7150
|
|
|
// record item as 'non-chapter' reference |
7151
|
|
|
$last_item_not_dir = $item->get_id(); |
7152
|
|
|
$last_item_not_dir_type = $item->get_type(); |
7153
|
|
|
$last_item_not_dir_max = $item->get_max(); |
7154
|
|
|
} |
7155
|
|
|
} else { |
7156
|
|
|
if ('dir' != $item->get_type()) { |
7157
|
|
|
// Current item is not a folder (but it is the first item) so record as last "non-chapter" item |
7158
|
|
|
$last_item_not_dir = $item->get_id(); |
7159
|
|
|
$last_item_not_dir_type = $item->get_type(); |
7160
|
|
|
$last_item_not_dir_max = $item->get_max(); |
7161
|
|
|
} |
7162
|
|
|
} |
7163
|
|
|
// Saving the item as "previous item" for the next loop |
7164
|
|
|
$previous_item_id = $item->get_id(); |
7165
|
|
|
$previous_item_max = $item->get_max(); |
7166
|
|
|
$previous_item_type = $item->get_type(); |
7167
|
|
|
} |
7168
|
|
|
} |
7169
|
|
|
} |
7170
|
|
|
|
7171
|
|
|
/** |
7172
|
|
|
* @param array $params |
7173
|
|
|
* |
7174
|
|
|
* @return int |
7175
|
|
|
*/ |
7176
|
|
|
public static function createCategory($params) |
7177
|
|
|
{ |
7178
|
|
|
$courseEntity = api_get_course_entity(api_get_course_int_id()); |
7179
|
|
|
|
7180
|
|
|
$item = new CLpCategory(); |
7181
|
|
|
$item |
7182
|
|
|
->setTitle($params['name']) |
7183
|
|
|
->setParent($courseEntity) |
7184
|
|
|
->addCourseLink($courseEntity, api_get_session_entity()) |
7185
|
|
|
; |
7186
|
|
|
|
7187
|
|
|
$repo = Container::getLpCategoryRepository(); |
7188
|
|
|
$repo->create($item); |
7189
|
|
|
|
7190
|
|
|
return $item->getIid(); |
7191
|
|
|
} |
7192
|
|
|
|
7193
|
|
|
/** |
7194
|
|
|
* @param array $params |
7195
|
|
|
*/ |
7196
|
|
|
public static function updateCategory($params) |
7197
|
|
|
{ |
7198
|
|
|
$em = Database::getManager(); |
7199
|
|
|
/** @var CLpCategory $item */ |
7200
|
|
|
$item = $em->find(CLpCategory::class, $params['id']); |
7201
|
|
|
if ($item) { |
7202
|
|
|
$item->setTitle($params['name']); |
7203
|
|
|
$em->persist($item); |
7204
|
|
|
$em->flush(); |
7205
|
|
|
} |
7206
|
|
|
} |
7207
|
|
|
|
7208
|
|
|
public static function moveUpCategory(int $id): void |
7209
|
|
|
{ |
7210
|
|
|
$em = Database::getManager(); |
7211
|
|
|
/** @var CLpCategory $item */ |
7212
|
|
|
$item = $em->find(CLpCategory::class, $id); |
7213
|
|
|
if ($item) { |
7214
|
|
|
$course = api_get_course_entity(); |
7215
|
|
|
$session = api_get_session_entity(); |
7216
|
|
|
|
7217
|
|
|
$link = $item->resourceNode->getResourceLinkByContext($course, $session); |
7218
|
|
|
|
7219
|
|
|
if ($link) { |
7220
|
|
|
$link->moveUpPosition(); |
7221
|
|
|
|
7222
|
|
|
$em->flush(); |
7223
|
|
|
} |
7224
|
|
|
} |
7225
|
|
|
} |
7226
|
|
|
|
7227
|
|
|
public static function moveDownCategory(int $id): void |
7228
|
|
|
{ |
7229
|
|
|
$em = Database::getManager(); |
7230
|
|
|
/** @var CLpCategory $item */ |
7231
|
|
|
$item = $em->find(CLpCategory::class, $id); |
7232
|
|
|
if ($item) { |
7233
|
|
|
$course = api_get_course_entity(); |
7234
|
|
|
$session = api_get_session_entity(); |
7235
|
|
|
|
7236
|
|
|
$link = $item->resourceNode->getResourceLinkByContext($course, $session); |
7237
|
|
|
|
7238
|
|
|
if ($link) { |
7239
|
|
|
$link->moveDownPosition(); |
7240
|
|
|
|
7241
|
|
|
$em->flush(); |
7242
|
|
|
} |
7243
|
|
|
} |
7244
|
|
|
} |
7245
|
|
|
|
7246
|
|
|
/** |
7247
|
|
|
* @param int $courseId |
7248
|
|
|
* |
7249
|
|
|
* @return int |
7250
|
|
|
*/ |
7251
|
|
|
public static function getCountCategories($courseId) |
7252
|
|
|
{ |
7253
|
|
|
if (empty($courseId)) { |
7254
|
|
|
return 0; |
7255
|
|
|
} |
7256
|
|
|
$repo = Container::getLpCategoryRepository(); |
7257
|
|
|
$qb = $repo->getResourcesByCourse(api_get_course_entity($courseId)); |
7258
|
|
|
$qb->addSelect('count(resource)'); |
7259
|
|
|
|
7260
|
|
|
return (int) $qb->getQuery()->getSingleScalarResult(); |
7261
|
|
|
} |
7262
|
|
|
|
7263
|
|
|
/** |
7264
|
|
|
* @param int $courseId |
7265
|
|
|
* |
7266
|
|
|
* @return CLpCategory[] |
7267
|
|
|
*/ |
7268
|
|
|
public static function getCategories($courseId) |
7269
|
|
|
{ |
7270
|
|
|
// Using doctrine extensions |
7271
|
|
|
$repo = Container::getLpCategoryRepository(); |
7272
|
|
|
$qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity(), null, null, true, true); |
7273
|
|
|
|
7274
|
|
|
return $qb->getQuery()->getResult(); |
7275
|
|
|
} |
7276
|
|
|
|
7277
|
|
|
public static function getCategorySessionId($id) |
7278
|
|
|
{ |
7279
|
|
|
if ('true' !== api_get_setting('lp.allow_session_lp_category')) { |
7280
|
|
|
return 0; |
7281
|
|
|
} |
7282
|
|
|
|
7283
|
|
|
$repo = Container::getLpCategoryRepository(); |
7284
|
|
|
/** @var CLpCategory $category */ |
7285
|
|
|
$category = $repo->find($id); |
7286
|
|
|
|
7287
|
|
|
$sessionId = 0; |
7288
|
|
|
$link = $category->getFirstResourceLink(); |
7289
|
|
|
if ($link && $link->getSession()) { |
7290
|
|
|
$sessionId = (int) $link->getSession()->getId(); |
7291
|
|
|
} |
7292
|
|
|
|
7293
|
|
|
return $sessionId; |
7294
|
|
|
} |
7295
|
|
|
|
7296
|
|
|
public static function deleteCategory(int $id): bool |
7297
|
|
|
{ |
7298
|
|
|
$repo = Container::getLpCategoryRepository(); |
7299
|
|
|
/** @var CLpCategory $category */ |
7300
|
|
|
$category = $repo->find($id); |
7301
|
|
|
if ($category) { |
7302
|
|
|
$em = Database::getManager(); |
7303
|
|
|
$lps = $category->getLps(); |
7304
|
|
|
|
7305
|
|
|
foreach ($lps as $lp) { |
7306
|
|
|
$lp->setCategory(null); |
7307
|
|
|
$em->persist($lp); |
7308
|
|
|
} |
7309
|
|
|
|
7310
|
|
|
$course = api_get_course_entity(); |
7311
|
|
|
$session = api_get_session_entity(); |
7312
|
|
|
|
7313
|
|
|
$em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session); |
7314
|
|
|
|
7315
|
|
|
return true; |
7316
|
|
|
} |
7317
|
|
|
|
7318
|
|
|
return false; |
7319
|
|
|
} |
7320
|
|
|
|
7321
|
|
|
/** |
7322
|
|
|
* @param int $courseId |
7323
|
|
|
* @param bool $addSelectOption |
7324
|
|
|
* |
7325
|
|
|
* @return array |
7326
|
|
|
*/ |
7327
|
|
|
public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false) |
7328
|
|
|
{ |
7329
|
|
|
$repo = Container::getLpCategoryRepository(); |
7330
|
|
|
$qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity()); |
7331
|
|
|
$items = $qb->getQuery()->getResult(); |
7332
|
|
|
|
7333
|
|
|
$cats = []; |
7334
|
|
|
if ($addSelectOption) { |
7335
|
|
|
$cats = [get_lang('Select a category')]; |
7336
|
|
|
} |
7337
|
|
|
|
7338
|
|
|
if (!empty($items)) { |
7339
|
|
|
foreach ($items as $cat) { |
7340
|
|
|
$cats[$cat->getIid()] = $cat->getTitle(); |
7341
|
|
|
} |
7342
|
|
|
} |
7343
|
|
|
|
7344
|
|
|
return $cats; |
7345
|
|
|
} |
7346
|
|
|
|
7347
|
|
|
/** |
7348
|
|
|
* @param int $courseId |
7349
|
|
|
* @param int $lpId |
7350
|
|
|
* @param int $user_id |
7351
|
|
|
* |
7352
|
|
|
* @return learnpath |
7353
|
|
|
*/ |
7354
|
|
|
public static function getLpFromSession(int $courseId, int $lpId, int $user_id) |
7355
|
|
|
{ |
7356
|
|
|
$debug = 0; |
7357
|
|
|
$learnPath = null; |
7358
|
|
|
$lpObject = Session::read('lpobject'); |
7359
|
|
|
|
7360
|
|
|
$repo = Container::getLpRepository(); |
7361
|
|
|
$lp = $repo->find($lpId); |
7362
|
|
|
if (null !== $lpObject) { |
7363
|
|
|
/** @var learnpath $learnPath */ |
7364
|
|
|
$learnPath = UnserializeApi::unserialize('lp', $lpObject); |
7365
|
|
|
$learnPath->entity = $lp; |
7366
|
|
|
if ($debug) { |
7367
|
|
|
error_log('getLpFromSession: unserialize'); |
7368
|
|
|
error_log('------getLpFromSession------'); |
7369
|
|
|
error_log('------unserialize------'); |
7370
|
|
|
error_log("lp_view_session_id: ".$learnPath->lp_view_session_id); |
7371
|
|
|
error_log("api_get_sessionid: ".api_get_session_id()); |
7372
|
|
|
} |
7373
|
|
|
} |
7374
|
|
|
|
7375
|
|
|
if (!is_object($learnPath)) { |
7376
|
|
|
$learnPath = new learnpath($lp, api_get_course_info_by_id($courseId), $user_id); |
7377
|
|
|
if ($debug) { |
7378
|
|
|
error_log('------getLpFromSession------'); |
7379
|
|
|
error_log('getLpFromSession: create new learnpath'); |
7380
|
|
|
error_log("create new LP with $courseId - $lpId - $user_id"); |
7381
|
|
|
error_log("lp_view_session_id: ".$learnPath->lp_view_session_id); |
7382
|
|
|
error_log("api_get_sessionid: ".api_get_session_id()); |
7383
|
|
|
} |
7384
|
|
|
} |
7385
|
|
|
|
7386
|
|
|
return $learnPath; |
7387
|
|
|
} |
7388
|
|
|
|
7389
|
|
|
/** |
7390
|
|
|
* @param int $itemId |
7391
|
|
|
* |
7392
|
|
|
* @return learnpathItem|false |
7393
|
|
|
*/ |
7394
|
|
|
public function getItem($itemId) |
7395
|
|
|
{ |
7396
|
|
|
if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) { |
7397
|
|
|
return $this->items[$itemId]; |
7398
|
|
|
} |
7399
|
|
|
|
7400
|
|
|
return false; |
7401
|
|
|
} |
7402
|
|
|
|
7403
|
|
|
/** |
7404
|
|
|
* @return int |
7405
|
|
|
*/ |
7406
|
|
|
public function getCurrentAttempt() |
7407
|
|
|
{ |
7408
|
|
|
$attempt = $this->getItem($this->get_current_item_id()); |
7409
|
|
|
if ($attempt) { |
7410
|
|
|
return $attempt->get_attempt_id(); |
7411
|
|
|
} |
7412
|
|
|
|
7413
|
|
|
return 0; |
7414
|
|
|
} |
7415
|
|
|
|
7416
|
|
|
/** |
7417
|
|
|
* @return int |
7418
|
|
|
*/ |
7419
|
|
|
public function getCategoryId() |
7420
|
|
|
{ |
7421
|
|
|
return (int) $this->categoryId; |
7422
|
|
|
} |
7423
|
|
|
|
7424
|
|
|
/** |
7425
|
|
|
* Get whether this is a learning path with the possibility to subscribe |
7426
|
|
|
* users or not. |
7427
|
|
|
* |
7428
|
|
|
* @return int |
7429
|
|
|
*/ |
7430
|
|
|
public function getSubscribeUsers() |
7431
|
|
|
{ |
7432
|
|
|
return $this->subscribeUsers; |
7433
|
|
|
} |
7434
|
|
|
|
7435
|
|
|
/** |
7436
|
|
|
* Calculate the count of stars for a user in this LP |
7437
|
|
|
* This calculation is based on the following rules: |
7438
|
|
|
* - the student gets one star when he gets to 50% of the learning path |
7439
|
|
|
* - the student gets a second star when the average score of all tests inside the learning path >= 50% |
7440
|
|
|
* - the student gets a third star when the average score of all tests inside the learning path >= 80% |
7441
|
|
|
* - the student gets the final star when the score for the *last* test is >= 80%. |
7442
|
|
|
* |
7443
|
|
|
* @param int $sessionId Optional. The session ID |
7444
|
|
|
* |
7445
|
|
|
* @return int The count of stars |
7446
|
|
|
*/ |
7447
|
|
|
public function getCalculateStars($sessionId = 0) |
7448
|
|
|
{ |
7449
|
|
|
$stars = 0; |
7450
|
|
|
$progress = self::getProgress( |
7451
|
|
|
$this->lp_id, |
7452
|
|
|
$this->user_id, |
7453
|
|
|
$this->course_int_id, |
7454
|
|
|
$sessionId |
7455
|
|
|
); |
7456
|
|
|
|
7457
|
|
|
if ($progress >= 50) { |
7458
|
|
|
$stars++; |
7459
|
|
|
} |
7460
|
|
|
|
7461
|
|
|
// Calculate stars chapters evaluation |
7462
|
|
|
$exercisesItems = $this->getExercisesItems(); |
7463
|
|
|
|
7464
|
|
|
if (!empty($exercisesItems)) { |
7465
|
|
|
$totalResult = 0; |
7466
|
|
|
|
7467
|
|
|
foreach ($exercisesItems as $exerciseItem) { |
7468
|
|
|
$exerciseResultInfo = Event::getExerciseResultsByUser( |
7469
|
|
|
$this->user_id, |
7470
|
|
|
$exerciseItem->path, |
7471
|
|
|
$this->course_int_id, |
7472
|
|
|
$sessionId, |
7473
|
|
|
$this->lp_id, |
7474
|
|
|
$exerciseItem->db_id |
7475
|
|
|
); |
7476
|
|
|
|
7477
|
|
|
$exerciseResultInfo = end($exerciseResultInfo); |
7478
|
|
|
|
7479
|
|
|
if (!$exerciseResultInfo) { |
7480
|
|
|
continue; |
7481
|
|
|
} |
7482
|
|
|
|
7483
|
|
|
if (!empty($exerciseResultInfo['max_score'])) { |
7484
|
|
|
$exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score']; |
7485
|
|
|
} else { |
7486
|
|
|
$exerciseResult = 0; |
7487
|
|
|
} |
7488
|
|
|
$totalResult += $exerciseResult; |
7489
|
|
|
} |
7490
|
|
|
|
7491
|
|
|
$totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1); |
7492
|
|
|
|
7493
|
|
|
if ($totalExerciseAverage >= 50) { |
7494
|
|
|
$stars++; |
7495
|
|
|
} |
7496
|
|
|
|
7497
|
|
|
if ($totalExerciseAverage >= 80) { |
7498
|
|
|
$stars++; |
7499
|
|
|
} |
7500
|
|
|
} |
7501
|
|
|
|
7502
|
|
|
// Calculate star for final evaluation |
7503
|
|
|
$finalEvaluationItem = $this->getFinalEvaluationItem(); |
7504
|
|
|
|
7505
|
|
|
if (!empty($finalEvaluationItem)) { |
7506
|
|
|
$evaluationResultInfo = Event::getExerciseResultsByUser( |
7507
|
|
|
$this->user_id, |
7508
|
|
|
$finalEvaluationItem->path, |
7509
|
|
|
$this->course_int_id, |
7510
|
|
|
$sessionId, |
7511
|
|
|
$this->lp_id, |
7512
|
|
|
$finalEvaluationItem->db_id |
7513
|
|
|
); |
7514
|
|
|
|
7515
|
|
|
$evaluationResultInfo = end($evaluationResultInfo); |
7516
|
|
|
|
7517
|
|
|
if ($evaluationResultInfo) { |
7518
|
|
|
$evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score']; |
7519
|
|
|
if ($evaluationResult >= 80) { |
7520
|
|
|
$stars++; |
7521
|
|
|
} |
7522
|
|
|
} |
7523
|
|
|
} |
7524
|
|
|
|
7525
|
|
|
return $stars; |
7526
|
|
|
} |
7527
|
|
|
|
7528
|
|
|
/** |
7529
|
|
|
* Get the items of exercise type. |
7530
|
|
|
* |
7531
|
|
|
* @return array The items. Otherwise return false |
7532
|
|
|
*/ |
7533
|
|
|
public function getExercisesItems() |
7534
|
|
|
{ |
7535
|
|
|
$exercises = []; |
7536
|
|
|
foreach ($this->items as $item) { |
7537
|
|
|
if ('quiz' !== $item->type) { |
7538
|
|
|
continue; |
7539
|
|
|
} |
7540
|
|
|
$exercises[] = $item; |
7541
|
|
|
} |
7542
|
|
|
|
7543
|
|
|
array_pop($exercises); |
7544
|
|
|
|
7545
|
|
|
return $exercises; |
7546
|
|
|
} |
7547
|
|
|
|
7548
|
|
|
/** |
7549
|
|
|
* Get the item of exercise type (evaluation type). |
7550
|
|
|
* |
7551
|
|
|
* @return array The final evaluation. Otherwise return false |
7552
|
|
|
*/ |
7553
|
|
|
public function getFinalEvaluationItem() |
7554
|
|
|
{ |
7555
|
|
|
$exercises = []; |
7556
|
|
|
foreach ($this->items as $item) { |
7557
|
|
|
if (TOOL_QUIZ !== $item->type) { |
7558
|
|
|
continue; |
7559
|
|
|
} |
7560
|
|
|
|
7561
|
|
|
$exercises[] = $item; |
7562
|
|
|
} |
7563
|
|
|
|
7564
|
|
|
return array_pop($exercises); |
7565
|
|
|
} |
7566
|
|
|
|
7567
|
|
|
/** |
7568
|
|
|
* Calculate the total points achieved for the current user in this learning path. |
7569
|
|
|
* |
7570
|
|
|
* @param int $sessionId Optional. The session Id |
7571
|
|
|
* |
7572
|
|
|
* @return int |
7573
|
|
|
*/ |
7574
|
|
|
public function getCalculateScore($sessionId = 0) |
7575
|
|
|
{ |
7576
|
|
|
// Calculate stars chapters evaluation |
7577
|
|
|
$exercisesItems = $this->getExercisesItems(); |
7578
|
|
|
$finalEvaluationItem = $this->getFinalEvaluationItem(); |
7579
|
|
|
$totalExercisesResult = 0; |
7580
|
|
|
$totalEvaluationResult = 0; |
7581
|
|
|
|
7582
|
|
|
if (false !== $exercisesItems) { |
7583
|
|
|
foreach ($exercisesItems as $exerciseItem) { |
7584
|
|
|
$exerciseResultInfo = Event::getExerciseResultsByUser( |
7585
|
|
|
$this->user_id, |
7586
|
|
|
$exerciseItem->path, |
7587
|
|
|
$this->course_int_id, |
7588
|
|
|
$sessionId, |
7589
|
|
|
$this->lp_id, |
7590
|
|
|
$exerciseItem->db_id |
7591
|
|
|
); |
7592
|
|
|
|
7593
|
|
|
$exerciseResultInfo = end($exerciseResultInfo); |
7594
|
|
|
|
7595
|
|
|
if (!$exerciseResultInfo) { |
7596
|
|
|
continue; |
7597
|
|
|
} |
7598
|
|
|
|
7599
|
|
|
$totalExercisesResult += $exerciseResultInfo['score']; |
7600
|
|
|
} |
7601
|
|
|
} |
7602
|
|
|
|
7603
|
|
|
if (!empty($finalEvaluationItem)) { |
7604
|
|
|
$evaluationResultInfo = Event::getExerciseResultsByUser( |
7605
|
|
|
$this->user_id, |
7606
|
|
|
$finalEvaluationItem->path, |
7607
|
|
|
$this->course_int_id, |
7608
|
|
|
$sessionId, |
7609
|
|
|
$this->lp_id, |
7610
|
|
|
$finalEvaluationItem->db_id |
7611
|
|
|
); |
7612
|
|
|
|
7613
|
|
|
$evaluationResultInfo = end($evaluationResultInfo); |
7614
|
|
|
|
7615
|
|
|
if ($evaluationResultInfo) { |
7616
|
|
|
$totalEvaluationResult += $evaluationResultInfo['score']; |
7617
|
|
|
} |
7618
|
|
|
} |
7619
|
|
|
|
7620
|
|
|
return $totalExercisesResult + $totalEvaluationResult; |
7621
|
|
|
} |
7622
|
|
|
|
7623
|
|
|
/** |
7624
|
|
|
* Check if URL is not allowed to be show in a iframe. |
7625
|
|
|
* |
7626
|
|
|
* @param string $src |
7627
|
|
|
* |
7628
|
|
|
* @return string |
7629
|
|
|
*/ |
7630
|
|
|
public function fixBlockedLinks($src) |
7631
|
|
|
{ |
7632
|
|
|
$urlInfo = parse_url($src); |
7633
|
|
|
|
7634
|
|
|
$platformProtocol = 'https'; |
7635
|
|
|
if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) { |
7636
|
|
|
$platformProtocol = 'http'; |
7637
|
|
|
} |
7638
|
|
|
|
7639
|
|
|
$protocolFixApplied = false; |
7640
|
|
|
//Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme |
7641
|
|
|
$scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null; |
7642
|
|
|
$host = isset($urlInfo['host']) ? $urlInfo['host'] : null; |
7643
|
|
|
|
7644
|
|
|
if ($platformProtocol != $scheme) { |
7645
|
|
|
Session::write('x_frame_source', $src); |
7646
|
|
|
$src = 'blank.php?error=x_frames_options'; |
7647
|
|
|
$protocolFixApplied = true; |
7648
|
|
|
} |
7649
|
|
|
|
7650
|
|
|
if (false == $protocolFixApplied) { |
7651
|
|
|
if (false === strpos(api_get_path(WEB_PATH), $host)) { |
7652
|
|
|
// Check X-Frame-Options |
7653
|
|
|
$ch = curl_init(); |
7654
|
|
|
$options = [ |
7655
|
|
|
CURLOPT_URL => $src, |
7656
|
|
|
CURLOPT_RETURNTRANSFER => true, |
7657
|
|
|
CURLOPT_HEADER => true, |
7658
|
|
|
CURLOPT_FOLLOWLOCATION => true, |
7659
|
|
|
CURLOPT_ENCODING => "", |
7660
|
|
|
CURLOPT_AUTOREFERER => true, |
7661
|
|
|
CURLOPT_CONNECTTIMEOUT => 120, |
7662
|
|
|
CURLOPT_TIMEOUT => 120, |
7663
|
|
|
CURLOPT_MAXREDIRS => 10, |
7664
|
|
|
]; |
7665
|
|
|
|
7666
|
|
|
$proxySettings = api_get_setting('platform.proxy_settings', true); |
7667
|
|
|
if (!empty($proxySettings) && |
7668
|
|
|
isset($proxySettings['curl_setopt_array']) |
7669
|
|
|
) { |
7670
|
|
|
$options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY']; |
7671
|
|
|
$options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT']; |
7672
|
|
|
} |
7673
|
|
|
|
7674
|
|
|
curl_setopt_array($ch, $options); |
7675
|
|
|
$response = curl_exec($ch); |
7676
|
|
|
$httpCode = curl_getinfo($ch); |
7677
|
|
|
$headers = substr($response, 0, $httpCode['header_size']); |
7678
|
|
|
|
7679
|
|
|
$error = false; |
7680
|
|
|
if (stripos($headers, 'X-Frame-Options: DENY') > -1 |
7681
|
|
|
//|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1 |
7682
|
|
|
) { |
7683
|
|
|
$error = true; |
7684
|
|
|
} |
7685
|
|
|
|
7686
|
|
|
if ($error) { |
7687
|
|
|
Session::write('x_frame_source', $src); |
7688
|
|
|
$src = 'blank.php?error=x_frames_options'; |
7689
|
|
|
} |
7690
|
|
|
} |
7691
|
|
|
} |
7692
|
|
|
|
7693
|
|
|
return $src; |
7694
|
|
|
} |
7695
|
|
|
|
7696
|
|
|
/** |
7697
|
|
|
* Check if this LP has a created forum in the basis course. |
7698
|
|
|
* |
7699
|
|
|
* @deprecated |
7700
|
|
|
* |
7701
|
|
|
* @return bool |
7702
|
|
|
*/ |
7703
|
|
|
public function lpHasForum() |
7704
|
|
|
{ |
7705
|
|
|
$forumTable = Database::get_course_table(TABLE_FORUM); |
7706
|
|
|
$itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY); |
7707
|
|
|
|
7708
|
|
|
$fakeFrom = " |
7709
|
|
|
$forumTable f |
7710
|
|
|
INNER JOIN $itemProperty ip |
7711
|
|
|
ON (f.forum_id = ip.ref AND f.c_id = ip.c_id) |
7712
|
|
|
"; |
7713
|
|
|
|
7714
|
|
|
$resultData = Database::select( |
7715
|
|
|
'COUNT(f.iid) AS qty', |
7716
|
|
|
$fakeFrom, |
7717
|
|
|
[ |
7718
|
|
|
'where' => [ |
7719
|
|
|
'ip.visibility != ? AND ' => 2, |
7720
|
|
|
'ip.tool = ? AND ' => TOOL_FORUM, |
7721
|
|
|
'f.c_id = ? AND ' => intval($this->course_int_id), |
7722
|
|
|
'f.lp_id = ?' => intval($this->lp_id), |
7723
|
|
|
], |
7724
|
|
|
], |
7725
|
|
|
'first' |
7726
|
|
|
); |
7727
|
|
|
|
7728
|
|
|
return $resultData['qty'] > 0; |
7729
|
|
|
} |
7730
|
|
|
|
7731
|
|
|
/** |
7732
|
|
|
* Get the forum for this learning path. |
7733
|
|
|
* |
7734
|
|
|
* @param int $sessionId |
7735
|
|
|
* |
7736
|
|
|
* @return array |
7737
|
|
|
*/ |
7738
|
|
|
public function getForum($sessionId = 0) |
7739
|
|
|
{ |
7740
|
|
|
$repo = Container::getForumRepository(); |
7741
|
|
|
|
7742
|
|
|
$course = api_get_course_entity(); |
7743
|
|
|
$session = api_get_session_entity($sessionId); |
7744
|
|
|
$qb = $repo->getResourcesByCourse($course, $session); |
7745
|
|
|
|
7746
|
|
|
return $qb->getQuery()->getResult(); |
7747
|
|
|
} |
7748
|
|
|
|
7749
|
|
|
/** |
7750
|
|
|
* Get the LP Final Item form. |
7751
|
|
|
* |
7752
|
|
|
* @throws Exception |
7753
|
|
|
* |
7754
|
|
|
* |
7755
|
|
|
* @return string |
7756
|
|
|
*/ |
7757
|
|
|
public function getFinalItemForm() |
7758
|
|
|
{ |
7759
|
|
|
$finalItem = $this->getFinalItem(); |
7760
|
|
|
$title = ''; |
7761
|
|
|
|
7762
|
|
|
if ($finalItem) { |
7763
|
|
|
$title = $finalItem->get_title(); |
7764
|
|
|
$buttonText = get_lang('Save'); |
7765
|
|
|
$content = $this->getSavedFinalItem(); |
7766
|
|
|
} else { |
7767
|
|
|
$buttonText = get_lang('Add this document to the course'); |
7768
|
|
|
$content = $this->getFinalItemTemplate(); |
7769
|
|
|
} |
7770
|
|
|
|
7771
|
|
|
$editorConfig = [ |
7772
|
|
|
'ToolbarSet' => 'LearningPathDocuments', |
7773
|
|
|
'Width' => '100%', |
7774
|
|
|
'Height' => '500', |
7775
|
|
|
'FullPage' => true, |
7776
|
|
|
]; |
7777
|
|
|
|
7778
|
|
|
$url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([ |
7779
|
|
|
'type' => 'document', |
7780
|
|
|
'lp_id' => $this->lp_id, |
7781
|
|
|
]); |
7782
|
|
|
|
7783
|
|
|
$form = new FormValidator('final_item', 'POST', $url); |
7784
|
|
|
$form->addText('title', get_lang('Title')); |
7785
|
|
|
$form->addButtonSave($buttonText); |
7786
|
|
|
$form->addHtml( |
7787
|
|
|
Display::return_message( |
7788
|
|
|
'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>', |
7789
|
|
|
'normal', |
7790
|
|
|
false |
7791
|
|
|
) |
7792
|
|
|
); |
7793
|
|
|
|
7794
|
|
|
$renderer = $form->defaultRenderer(); |
7795
|
|
|
$renderer->setElementTemplate(' {label}{element}', 'content_lp_certificate'); |
7796
|
|
|
|
7797
|
|
|
$form->addHtmlEditor( |
7798
|
|
|
'content_lp_certificate', |
7799
|
|
|
null, |
7800
|
|
|
true, |
7801
|
|
|
false, |
7802
|
|
|
$editorConfig |
7803
|
|
|
); |
7804
|
|
|
$form->addHidden('action', 'add_final_item'); |
7805
|
|
|
$form->addHidden('path', Session::read('pathItem')); |
7806
|
|
|
$form->addHidden('previous', $this->get_last()); |
7807
|
|
|
$form->setDefaults( |
7808
|
|
|
['title' => $title, 'content_lp_certificate' => $content] |
7809
|
|
|
); |
7810
|
|
|
|
7811
|
|
|
if ($form->validate()) { |
7812
|
|
|
$values = $form->exportValues(); |
7813
|
|
|
$lastItemId = $this->getLastInFirstLevel(); |
7814
|
|
|
|
7815
|
|
|
if (!$finalItem) { |
7816
|
|
|
$documentId = $this->create_document( |
7817
|
|
|
$this->course_info, |
7818
|
|
|
$values['content_lp_certificate'], |
7819
|
|
|
$values['title'] |
7820
|
|
|
); |
7821
|
|
|
$this->add_item( |
7822
|
|
|
null, |
7823
|
|
|
$lastItemId, |
7824
|
|
|
'final_item', |
7825
|
|
|
$documentId, |
7826
|
|
|
$values['title'], |
7827
|
|
|
); |
7828
|
|
|
|
7829
|
|
|
Display::addFlash( |
7830
|
|
|
Display::return_message(get_lang('Added')) |
7831
|
|
|
); |
7832
|
|
|
} else { |
7833
|
|
|
$this->edit_document(); |
7834
|
|
|
} |
7835
|
|
|
} |
7836
|
|
|
|
7837
|
|
|
return $form->returnForm(); |
7838
|
|
|
} |
7839
|
|
|
|
7840
|
|
|
/** |
7841
|
|
|
* Check if the current lp item is first, both, last or none from lp list. |
7842
|
|
|
* |
7843
|
|
|
* @param int $currentItemId |
7844
|
|
|
* |
7845
|
|
|
* @return string |
7846
|
|
|
*/ |
7847
|
|
|
public function isFirstOrLastItem($currentItemId) |
7848
|
|
|
{ |
7849
|
|
|
$lpItemId = []; |
7850
|
|
|
$typeListNotToVerify = self::getChapterTypes(); |
7851
|
|
|
|
7852
|
|
|
// Using get_toc() function instead $this->items because returns the correct order of the items |
7853
|
|
|
foreach ($this->get_toc() as $item) { |
7854
|
|
|
if (!in_array($item['type'], $typeListNotToVerify)) { |
7855
|
|
|
$lpItemId[] = $item['id']; |
7856
|
|
|
} |
7857
|
|
|
} |
7858
|
|
|
|
7859
|
|
|
$lastLpItemIndex = count($lpItemId) - 1; |
7860
|
|
|
$position = array_search($currentItemId, $lpItemId); |
7861
|
|
|
|
7862
|
|
|
switch ($position) { |
7863
|
|
|
case 0: |
7864
|
|
|
if (!$lastLpItemIndex) { |
7865
|
|
|
$answer = 'both'; |
7866
|
|
|
break; |
7867
|
|
|
} |
7868
|
|
|
|
7869
|
|
|
$answer = 'first'; |
7870
|
|
|
break; |
7871
|
|
|
case $lastLpItemIndex: |
7872
|
|
|
$answer = 'last'; |
7873
|
|
|
break; |
7874
|
|
|
default: |
7875
|
|
|
$answer = 'none'; |
7876
|
|
|
} |
7877
|
|
|
|
7878
|
|
|
return $answer; |
7879
|
|
|
} |
7880
|
|
|
|
7881
|
|
|
/** |
7882
|
|
|
* Get whether this is a learning path with the accumulated SCORM time or not. |
7883
|
|
|
* |
7884
|
|
|
* @return int |
7885
|
|
|
*/ |
7886
|
|
|
public function getAccumulateScormTime() |
7887
|
|
|
{ |
7888
|
|
|
return $this->accumulateScormTime; |
7889
|
|
|
} |
7890
|
|
|
|
7891
|
|
|
/** |
7892
|
|
|
* Returns an HTML-formatted link to a resource, to incorporate directly into |
7893
|
|
|
* the new learning path tool. |
7894
|
|
|
* |
7895
|
|
|
* The function is a big switch on tool type. |
7896
|
|
|
* In each case, we query the corresponding table for information and build the link |
7897
|
|
|
* with that information. |
7898
|
|
|
* |
7899
|
|
|
* @author Yannick Warnier <[email protected]> - rebranding based on |
7900
|
|
|
* previous work (display_addedresource_link_in_learnpath()) |
7901
|
|
|
* |
7902
|
|
|
* @param int $course_id Course code |
7903
|
|
|
* @param int $learningPathId The learning path ID (in lp table) |
7904
|
|
|
* @param int $id_in_path the unique index in the items table |
7905
|
|
|
* @param int $lpViewId |
7906
|
|
|
* |
7907
|
|
|
* @return string |
7908
|
|
|
*/ |
7909
|
|
|
public static function rl_get_resource_link_for_learnpath( |
7910
|
|
|
$course_id, |
7911
|
|
|
$learningPathId, |
7912
|
|
|
$id_in_path, |
7913
|
|
|
$lpViewId |
7914
|
|
|
) { |
7915
|
|
|
$session_id = api_get_session_id(); |
7916
|
|
|
|
7917
|
|
|
$learningPathId = (int) $learningPathId; |
7918
|
|
|
$id_in_path = (int) $id_in_path; |
7919
|
|
|
$lpViewId = (int) $lpViewId; |
7920
|
|
|
|
7921
|
|
|
$em = Database::getManager(); |
7922
|
|
|
$lpItemRepo = $em->getRepository(CLpItem::class); |
7923
|
|
|
|
7924
|
|
|
/** @var CLpItem $rowItem */ |
7925
|
|
|
$rowItem = $lpItemRepo->findOneBy([ |
7926
|
|
|
'lp' => $learningPathId, |
7927
|
|
|
'iid' => $id_in_path, |
7928
|
|
|
]); |
7929
|
|
|
$type = $rowItem->getItemType(); |
7930
|
|
|
$id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath(); |
7931
|
|
|
$main_dir_path = api_get_path(WEB_CODE_PATH); |
7932
|
|
|
$link = ''; |
7933
|
|
|
$extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id; |
7934
|
|
|
|
7935
|
|
|
switch ($type) { |
7936
|
|
|
case 'dir': |
7937
|
|
|
return $main_dir_path.'lp/blank.php'; |
7938
|
|
|
case TOOL_CALENDAR_EVENT: |
7939
|
|
|
return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams; |
7940
|
|
|
case TOOL_ANNOUNCEMENT: |
7941
|
|
|
return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams; |
7942
|
|
|
case TOOL_LINK: |
7943
|
|
|
$linkInfo = Link::getLinkInfo($id); |
7944
|
|
|
if (isset($linkInfo['url'])) { |
7945
|
|
|
return $linkInfo['url']; |
7946
|
|
|
} |
7947
|
|
|
|
7948
|
|
|
return ''; |
7949
|
|
|
case TOOL_QUIZ: |
7950
|
|
|
if (empty($id)) { |
7951
|
|
|
return ''; |
7952
|
|
|
} |
7953
|
|
|
|
7954
|
|
|
// Get the lp_item_view with the highest view_count. |
7955
|
|
|
$learnpathItemViewResult = $em |
7956
|
|
|
->getRepository(CLpItemView::class) |
7957
|
|
|
->findBy( |
7958
|
|
|
['item' => $rowItem->getIid(), 'view' => $lpViewId], |
7959
|
|
|
['viewCount' => 'DESC'], |
7960
|
|
|
1 |
7961
|
|
|
); |
7962
|
|
|
/** @var CLpItemView $learnpathItemViewData */ |
7963
|
|
|
$learnpathItemViewData = current($learnpathItemViewResult); |
7964
|
|
|
$learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0; |
7965
|
|
|
|
7966
|
|
|
return $main_dir_path.'exercise/overview.php?'.$extraParams.'&' |
7967
|
|
|
.http_build_query([ |
7968
|
|
|
'lp_init' => 1, |
7969
|
|
|
'learnpath_item_view_id' => $learnpathItemViewId, |
7970
|
|
|
'learnpath_id' => $learningPathId, |
7971
|
|
|
'learnpath_item_id' => $id_in_path, |
7972
|
|
|
'exerciseId' => $id, |
7973
|
|
|
]); |
7974
|
|
|
case TOOL_HOTPOTATOES: |
7975
|
|
|
return ''; |
7976
|
|
|
case TOOL_FORUM: |
7977
|
|
|
return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams; |
7978
|
|
|
case TOOL_THREAD: |
7979
|
|
|
// forum post |
7980
|
|
|
$tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD); |
7981
|
|
|
if (empty($id)) { |
7982
|
|
|
return ''; |
7983
|
|
|
} |
7984
|
|
|
$sql = "SELECT * FROM $tbl_topics WHERE iid=$id"; |
7985
|
|
|
$result = Database::query($sql); |
7986
|
|
|
$row = Database::fetch_array($result); |
7987
|
|
|
|
7988
|
|
|
return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$row['forum_id'].'&lp=true&' |
7989
|
|
|
.$extraParams; |
7990
|
|
|
case TOOL_POST: |
7991
|
|
|
$tbl_post = Database::get_course_table(TABLE_FORUM_POST); |
7992
|
|
|
$result = Database::query("SELECT * FROM $tbl_post WHERE post_id=$id"); |
7993
|
|
|
$row = Database::fetch_array($result); |
7994
|
|
|
|
7995
|
|
|
return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$row['thread_id'].'&forum=' |
7996
|
|
|
.$row['forum_id'].'&lp=true&'.$extraParams; |
7997
|
|
|
case TOOL_READOUT_TEXT: |
7998
|
|
|
return api_get_path(WEB_CODE_PATH). |
7999
|
|
|
'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams; |
8000
|
|
|
case TOOL_DOCUMENT: |
8001
|
|
|
$repo = Container::getDocumentRepository(); |
8002
|
|
|
$document = $repo->find($rowItem->getPath()); |
8003
|
|
|
if ($document) { |
8004
|
|
|
$params = [ |
8005
|
|
|
'cid' => $course_id, |
8006
|
|
|
'sid' => $session_id, |
8007
|
|
|
]; |
8008
|
|
|
|
8009
|
|
|
return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL); |
8010
|
|
|
} |
8011
|
|
|
|
8012
|
|
|
return null; |
8013
|
|
|
case TOOL_LP_FINAL_ITEM: |
8014
|
|
|
return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&' |
8015
|
|
|
.$extraParams; |
8016
|
|
|
case 'assignments': |
8017
|
|
|
return $main_dir_path.'work/work.php?'.$extraParams; |
8018
|
|
|
case TOOL_DROPBOX: |
8019
|
|
|
return $main_dir_path.'dropbox/index.php?'.$extraParams; |
8020
|
|
|
case 'introduction_text': //DEPRECATED |
8021
|
|
|
return ''; |
8022
|
|
|
case TOOL_COURSE_DESCRIPTION: |
8023
|
|
|
return $main_dir_path.'course_description?'.$extraParams; |
8024
|
|
|
case TOOL_GROUP: |
8025
|
|
|
return $main_dir_path.'group/group.php?'.$extraParams; |
8026
|
|
|
case TOOL_USER: |
8027
|
|
|
return $main_dir_path.'user/user.php?'.$extraParams; |
8028
|
|
|
case TOOL_STUDENTPUBLICATION: |
8029
|
|
|
$repo = Container::getStudentPublicationRepository(); |
8030
|
|
|
$publication = $repo->find($rowItem->getPath()); |
8031
|
|
|
if ($publication && $publication->hasResourceNode()) { |
8032
|
|
|
$nodeId = $publication->getResourceNode()->getId(); |
8033
|
|
|
$assignmentId = $publication->getIid(); |
8034
|
|
|
|
8035
|
|
|
return api_get_path(WEB_PATH) . |
8036
|
|
|
"resources/assignment/$nodeId/submission/$assignmentId?" . |
8037
|
|
|
http_build_query([ |
8038
|
|
|
'cid' => $course_id, |
8039
|
|
|
'sid' => $session_id, |
8040
|
|
|
'gid' => 0, |
8041
|
|
|
'origin' => 'learnpath', |
8042
|
|
|
'isStudentView' => 'true', |
8043
|
|
|
]); |
8044
|
|
|
} |
8045
|
|
|
return ''; |
8046
|
|
|
case TOOL_SURVEY: |
8047
|
|
|
|
8048
|
|
|
$surveyId = (int) $id; |
8049
|
|
|
$repo = Container::getSurveyRepository(); |
8050
|
|
|
if (!empty($surveyId)) { |
8051
|
|
|
/** @var CSurvey $survey */ |
8052
|
|
|
$survey = $repo->find($surveyId); |
8053
|
|
|
$autoSurveyLink = SurveyUtil::generateFillSurveyLink( |
8054
|
|
|
$survey, |
8055
|
|
|
'auto', |
8056
|
|
|
api_get_course_entity($course_id), |
8057
|
|
|
$session_id |
8058
|
|
|
); |
8059
|
|
|
$lpParams = [ |
8060
|
|
|
'lp_id' => $learningPathId, |
8061
|
|
|
'lp_item_id' => $id_in_path, |
8062
|
|
|
'origin' => 'learnpath', |
8063
|
|
|
]; |
8064
|
|
|
|
8065
|
|
|
return $autoSurveyLink.'&'.http_build_query($lpParams).'&'.$extraParams; |
8066
|
|
|
} |
8067
|
|
|
} |
8068
|
|
|
|
8069
|
|
|
return $link; |
8070
|
|
|
} |
8071
|
|
|
|
8072
|
|
|
/** |
8073
|
|
|
* Checks if any forum items in a given learning path are from the base course. |
8074
|
|
|
*/ |
8075
|
|
|
public static function isForumFromBaseCourse(int $learningPathId): bool |
8076
|
|
|
{ |
8077
|
|
|
$itemRepository = Container::getLpItemRepository(); |
8078
|
|
|
$forumRepository = Container::getForumRepository(); |
8079
|
|
|
$forums = $itemRepository->findItemsByLearningPathAndType($learningPathId, 'forum'); |
8080
|
|
|
|
8081
|
|
|
/* @var CLpItem $forumItem */ |
8082
|
|
|
foreach ($forums as $forumItem) { |
8083
|
|
|
$forumId = (int) $forumItem->getPath(); |
8084
|
|
|
$forum = $forumRepository->find($forumId); |
8085
|
|
|
|
8086
|
|
|
if ($forum !== null) { |
8087
|
|
|
$forumSession = $forum->getFirstResourceLink()->getSession(); |
8088
|
|
|
if ($forumSession === null) { |
8089
|
|
|
return true; |
8090
|
|
|
} |
8091
|
|
|
} |
8092
|
|
|
} |
8093
|
|
|
|
8094
|
|
|
return false; |
8095
|
|
|
} |
8096
|
|
|
|
8097
|
|
|
/** |
8098
|
|
|
* Gets the name of a resource (generally used in learnpath when no name is provided). |
8099
|
|
|
* |
8100
|
|
|
* @author Yannick Warnier <[email protected]> |
8101
|
|
|
* |
8102
|
|
|
* @param string $course_code Course code |
8103
|
|
|
* @param int $learningPathId |
8104
|
|
|
* @param int $id_in_path The resource ID |
8105
|
|
|
* |
8106
|
|
|
* @return string |
8107
|
|
|
*/ |
8108
|
|
|
public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path) |
8109
|
|
|
{ |
8110
|
|
|
$_course = api_get_course_info($course_code); |
8111
|
|
|
if (empty($_course)) { |
8112
|
|
|
return ''; |
8113
|
|
|
} |
8114
|
|
|
$course_id = $_course['real_id']; |
8115
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
8116
|
|
|
$learningPathId = (int) $learningPathId; |
8117
|
|
|
$id_in_path = (int) $id_in_path; |
8118
|
|
|
|
8119
|
|
|
$sql = "SELECT item_type, title, ref |
8120
|
|
|
FROM $tbl_lp_item |
8121
|
|
|
WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path"; |
8122
|
|
|
$res_item = Database::query($sql); |
8123
|
|
|
|
8124
|
|
|
if (Database::num_rows($res_item) < 1) { |
8125
|
|
|
return ''; |
8126
|
|
|
} |
8127
|
|
|
$row_item = Database::fetch_array($res_item); |
8128
|
|
|
$type = strtolower($row_item['item_type']); |
8129
|
|
|
$id = $row_item['ref']; |
8130
|
|
|
$output = ''; |
8131
|
|
|
|
8132
|
|
|
switch ($type) { |
8133
|
|
|
case TOOL_CALENDAR_EVENT: |
8134
|
|
|
$TABLEAGENDA = Database::get_course_table(TABLE_AGENDA); |
8135
|
|
|
$result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id"); |
8136
|
|
|
$myrow = Database::fetch_array($result); |
8137
|
|
|
$output = $myrow['title']; |
8138
|
|
|
break; |
8139
|
|
|
case TOOL_ANNOUNCEMENT: |
8140
|
|
|
$tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT); |
8141
|
|
|
$result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id"); |
8142
|
|
|
$myrow = Database::fetch_array($result); |
8143
|
|
|
$output = $myrow['title']; |
8144
|
|
|
break; |
8145
|
|
|
case TOOL_LINK: |
8146
|
|
|
// Doesn't take $target into account. |
8147
|
|
|
$TABLETOOLLINK = Database::get_course_table(TABLE_LINK); |
8148
|
|
|
$result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id"); |
8149
|
|
|
$myrow = Database::fetch_array($result); |
8150
|
|
|
$output = $myrow['title']; |
8151
|
|
|
break; |
8152
|
|
|
case TOOL_QUIZ: |
8153
|
|
|
$TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST); |
8154
|
|
|
$result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id"); |
8155
|
|
|
$myrow = Database::fetch_array($result); |
8156
|
|
|
$output = $myrow['title']; |
8157
|
|
|
break; |
8158
|
|
|
case TOOL_FORUM: |
8159
|
|
|
$TBL_FORUMS = Database::get_course_table(TABLE_FORUM); |
8160
|
|
|
$result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id"); |
8161
|
|
|
$myrow = Database::fetch_array($result); |
8162
|
|
|
$output = $myrow['title']; |
8163
|
|
|
break; |
8164
|
|
|
case TOOL_THREAD: |
8165
|
|
|
$tbl_post = Database::get_course_table(TABLE_FORUM_POST); |
8166
|
|
|
// Grabbing the title of the post. |
8167
|
|
|
$sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id; |
8168
|
|
|
$result_title = Database::query($sql_title); |
8169
|
|
|
$myrow_title = Database::fetch_array($result_title); |
8170
|
|
|
$output = $myrow_title['title']; |
8171
|
|
|
break; |
8172
|
|
|
case TOOL_POST: |
8173
|
|
|
$tbl_post = Database::get_course_table(TABLE_FORUM_POST); |
8174
|
|
|
$sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id"; |
8175
|
|
|
$result = Database::query($sql); |
8176
|
|
|
$post = Database::fetch_array($result); |
8177
|
|
|
$output = $post['title']; |
8178
|
|
|
break; |
8179
|
|
|
case 'dir': |
8180
|
|
|
case TOOL_DOCUMENT: |
8181
|
|
|
$title = $row_item['title']; |
8182
|
|
|
$output = '-'; |
8183
|
|
|
if (!empty($title)) { |
8184
|
|
|
$output = $title; |
8185
|
|
|
} |
8186
|
|
|
break; |
8187
|
|
|
case 'hotpotatoes': |
8188
|
|
|
$tbl_doc = Database::get_course_table(TABLE_DOCUMENT); |
8189
|
|
|
$result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id"); |
8190
|
|
|
$myrow = Database::fetch_array($result); |
8191
|
|
|
$pathname = explode('/', $myrow['path']); // Making a correct name for the link. |
8192
|
|
|
$last = count($pathname) - 1; // Making a correct name for the link. |
8193
|
|
|
$filename = $pathname[$last]; // Making a correct name for the link. |
8194
|
|
|
$myrow['path'] = rawurlencode($myrow['path']); |
8195
|
|
|
$output = $filename; |
8196
|
|
|
break; |
8197
|
|
|
} |
8198
|
|
|
|
8199
|
|
|
return stripslashes($output); |
8200
|
|
|
} |
8201
|
|
|
|
8202
|
|
|
/** |
8203
|
|
|
* Get the parent names for the current item. |
8204
|
|
|
* |
8205
|
|
|
* @param int $newItemId Optional. The item ID |
8206
|
|
|
*/ |
8207
|
|
|
public function getCurrentItemParentNames($newItemId = 0): array |
8208
|
|
|
{ |
8209
|
|
|
$newItemId = $newItemId ?: $this->get_current_item_id(); |
8210
|
|
|
$return = []; |
8211
|
|
|
$item = $this->getItem($newItemId); |
8212
|
|
|
|
8213
|
|
|
$parent = null; |
8214
|
|
|
if ($item) { |
8215
|
|
|
$parent = $this->getItem($item->get_parent()); |
8216
|
|
|
} |
8217
|
|
|
|
8218
|
|
|
while ($parent) { |
8219
|
|
|
$return[] = $parent->get_title(); |
8220
|
|
|
$parent = $this->getItem($parent->get_parent()); |
8221
|
|
|
} |
8222
|
|
|
|
8223
|
|
|
return array_reverse($return); |
8224
|
|
|
} |
8225
|
|
|
|
8226
|
|
|
/** |
8227
|
|
|
* Reads and process "lp_subscription_settings" setting. |
8228
|
|
|
* |
8229
|
|
|
* @return array |
8230
|
|
|
*/ |
8231
|
|
|
public static function getSubscriptionSettings() |
8232
|
|
|
{ |
8233
|
|
|
$subscriptionSettings = api_get_setting('lp.lp_subscription_settings', true); |
8234
|
|
|
if (!is_array($subscriptionSettings)) { |
8235
|
|
|
// By default, allow both settings |
8236
|
|
|
$subscriptionSettings = [ |
8237
|
|
|
'allow_add_users_to_lp' => true, |
8238
|
|
|
'allow_add_users_to_lp_category' => true, |
8239
|
|
|
]; |
8240
|
|
|
} else { |
8241
|
|
|
$subscriptionSettings = $subscriptionSettings['options']; |
8242
|
|
|
} |
8243
|
|
|
|
8244
|
|
|
return $subscriptionSettings; |
8245
|
|
|
} |
8246
|
|
|
|
8247
|
|
|
/** |
8248
|
|
|
* Exports a LP to a courseBuilder zip file. It adds the documents related to the LP. |
8249
|
|
|
*/ |
8250
|
|
|
public function exportToCourseBuildFormat() |
8251
|
|
|
{ |
8252
|
|
|
if (!api_is_allowed_to_edit()) { |
8253
|
|
|
return false; |
8254
|
|
|
} |
8255
|
|
|
|
8256
|
|
|
$courseBuilder = new CourseBuilder(); |
8257
|
|
|
$itemList = []; |
8258
|
|
|
/** @var learnpathItem $item */ |
8259
|
|
|
foreach ($this->items as $item) { |
8260
|
|
|
$itemList[$item->get_type()][] = $item->get_path(); |
8261
|
|
|
} |
8262
|
|
|
|
8263
|
|
|
if (empty($itemList)) { |
8264
|
|
|
return false; |
8265
|
|
|
} |
8266
|
|
|
|
8267
|
|
|
if (isset($itemList['document'])) { |
8268
|
|
|
// Get parents |
8269
|
|
|
foreach ($itemList['document'] as $documentId) { |
8270
|
|
|
$documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true); |
8271
|
|
|
if (!empty($documentInfo['parents'])) { |
8272
|
|
|
foreach ($documentInfo['parents'] as $parentInfo) { |
8273
|
|
|
if (in_array($parentInfo['iid'], $itemList['document'])) { |
8274
|
|
|
continue; |
8275
|
|
|
} |
8276
|
|
|
$itemList['document'][] = $parentInfo['iid']; |
8277
|
|
|
} |
8278
|
|
|
} |
8279
|
|
|
} |
8280
|
|
|
|
8281
|
|
|
$courseInfo = api_get_course_info(); |
8282
|
|
|
foreach ($itemList['document'] as $documentId) { |
8283
|
|
|
$documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id()); |
8284
|
|
|
$items = DocumentManager::get_resources_from_source_html( |
8285
|
|
|
$documentInfo['absolute_path'], |
8286
|
|
|
true, |
8287
|
|
|
TOOL_DOCUMENT |
8288
|
|
|
); |
8289
|
|
|
|
8290
|
|
|
if (!empty($items)) { |
8291
|
|
|
foreach ($items as $item) { |
8292
|
|
|
// Get information about source url |
8293
|
|
|
$url = $item[0]; // url |
8294
|
|
|
$scope = $item[1]; // scope (local, remote) |
8295
|
|
|
$type = $item[2]; // type (rel, abs, url) |
8296
|
|
|
|
8297
|
|
|
$origParseUrl = parse_url($url); |
8298
|
|
|
$realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null; |
8299
|
|
|
|
8300
|
|
|
if ('local' === $scope) { |
8301
|
|
|
if ('abs' === $type || 'rel' === $type) { |
8302
|
|
|
$documentFile = strstr($realOrigPath, 'document'); |
8303
|
|
|
if (false !== strpos($realOrigPath, $documentFile)) { |
8304
|
|
|
$documentFile = str_replace('document', '', $documentFile); |
8305
|
|
|
$itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile); |
8306
|
|
|
// Document found! Add it to the list |
8307
|
|
|
if ($itemDocumentId) { |
8308
|
|
|
$itemList['document'][] = $itemDocumentId; |
8309
|
|
|
} |
8310
|
|
|
} |
8311
|
|
|
} |
8312
|
|
|
} |
8313
|
|
|
} |
8314
|
|
|
} |
8315
|
|
|
} |
8316
|
|
|
|
8317
|
|
|
$courseBuilder->build_documents( |
8318
|
|
|
api_get_session_id(), |
8319
|
|
|
$this->get_course_int_id(), |
8320
|
|
|
true, |
8321
|
|
|
$itemList['document'] |
8322
|
|
|
); |
8323
|
|
|
} |
8324
|
|
|
|
8325
|
|
|
if (isset($itemList['quiz'])) { |
8326
|
|
|
$courseBuilder->build_quizzes( |
8327
|
|
|
api_get_session_id(), |
8328
|
|
|
$this->get_course_int_id(), |
8329
|
|
|
true, |
8330
|
|
|
$itemList['quiz'] |
8331
|
|
|
); |
8332
|
|
|
} |
8333
|
|
|
|
8334
|
|
|
if (!empty($itemList['thread'])) { |
8335
|
|
|
$threadList = []; |
8336
|
|
|
$repo = Container::getForumThreadRepository(); |
8337
|
|
|
foreach ($itemList['thread'] as $threadId) { |
8338
|
|
|
/** @var CForumThread $thread */ |
8339
|
|
|
$thread = $repo->find($threadId); |
8340
|
|
|
if ($thread) { |
8341
|
|
|
$itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0; |
8342
|
|
|
$threadList[] = $thread->getIid(); |
8343
|
|
|
} |
8344
|
|
|
} |
8345
|
|
|
|
8346
|
|
|
if (!empty($threadList)) { |
8347
|
|
|
$courseBuilder->build_forum_topics( |
8348
|
|
|
api_get_session_id(), |
8349
|
|
|
$this->get_course_int_id(), |
8350
|
|
|
null, |
8351
|
|
|
$threadList |
8352
|
|
|
); |
8353
|
|
|
} |
8354
|
|
|
} |
8355
|
|
|
|
8356
|
|
|
$forumCategoryList = []; |
8357
|
|
|
if (isset($itemList['forum'])) { |
8358
|
|
|
foreach ($itemList['forum'] as $forumId) { |
8359
|
|
|
$forumInfo = get_forums($forumId); |
8360
|
|
|
$forumCategoryList[] = $forumInfo['forum_category']; |
8361
|
|
|
} |
8362
|
|
|
} |
8363
|
|
|
|
8364
|
|
|
if (!empty($forumCategoryList)) { |
8365
|
|
|
$courseBuilder->build_forum_category( |
8366
|
|
|
api_get_session_id(), |
8367
|
|
|
$this->get_course_int_id(), |
8368
|
|
|
true, |
8369
|
|
|
$forumCategoryList |
8370
|
|
|
); |
8371
|
|
|
} |
8372
|
|
|
|
8373
|
|
|
if (!empty($itemList['forum'])) { |
8374
|
|
|
$courseBuilder->build_forums( |
8375
|
|
|
api_get_session_id(), |
8376
|
|
|
$this->get_course_int_id(), |
8377
|
|
|
true, |
8378
|
|
|
$itemList['forum'] |
8379
|
|
|
); |
8380
|
|
|
} |
8381
|
|
|
|
8382
|
|
|
if (isset($itemList['link'])) { |
8383
|
|
|
$courseBuilder->build_links( |
8384
|
|
|
api_get_session_id(), |
8385
|
|
|
$this->get_course_int_id(), |
8386
|
|
|
true, |
8387
|
|
|
$itemList['link'] |
8388
|
|
|
); |
8389
|
|
|
} |
8390
|
|
|
|
8391
|
|
|
if (!empty($itemList['student_publication'])) { |
8392
|
|
|
$courseBuilder->build_works( |
8393
|
|
|
api_get_session_id(), |
8394
|
|
|
$this->get_course_int_id(), |
8395
|
|
|
true, |
8396
|
|
|
$itemList['student_publication'] |
8397
|
|
|
); |
8398
|
|
|
} |
8399
|
|
|
|
8400
|
|
|
$courseBuilder->build_learnpaths( |
8401
|
|
|
api_get_session_id(), |
8402
|
|
|
$this->get_course_int_id(), |
8403
|
|
|
true, |
8404
|
|
|
[$this->get_id()], |
8405
|
|
|
false |
8406
|
|
|
); |
8407
|
|
|
|
8408
|
|
|
$courseBuilder->restoreDocumentsFromList(); |
8409
|
|
|
|
8410
|
|
|
$zipFile = CourseArchiver::createBackup($courseBuilder->course); |
8411
|
|
|
$zipPath = CourseArchiver::getBackupDir().$zipFile; |
8412
|
|
|
$result = DocumentManager::file_send_for_download( |
8413
|
|
|
$zipPath, |
8414
|
|
|
true, |
8415
|
|
|
$this->get_name().'.zip' |
8416
|
|
|
); |
8417
|
|
|
|
8418
|
|
|
if ($result) { |
8419
|
|
|
api_not_allowed(); |
8420
|
|
|
} |
8421
|
|
|
|
8422
|
|
|
return true; |
8423
|
|
|
} |
8424
|
|
|
|
8425
|
|
|
/** |
8426
|
|
|
* Get whether this is a learning path with the accumulated work time or not. |
8427
|
|
|
* |
8428
|
|
|
* @return int |
8429
|
|
|
*/ |
8430
|
|
|
public function getAccumulateWorkTime() |
8431
|
|
|
{ |
8432
|
|
|
return (int) $this->accumulateWorkTime; |
8433
|
|
|
} |
8434
|
|
|
|
8435
|
|
|
/** |
8436
|
|
|
* Get whether this is a learning path with the accumulated work time or not. |
8437
|
|
|
* |
8438
|
|
|
* @return int |
8439
|
|
|
*/ |
8440
|
|
|
public function getAccumulateWorkTimeTotalCourse() |
8441
|
|
|
{ |
8442
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
8443
|
|
|
$sql = "SELECT SUM(accumulate_work_time) AS total |
8444
|
|
|
FROM $table |
8445
|
|
|
WHERE c_id = ".$this->course_int_id; |
8446
|
|
|
$result = Database::query($sql); |
8447
|
|
|
$row = Database::fetch_array($result); |
8448
|
|
|
|
8449
|
|
|
return (int) $row['total']; |
8450
|
|
|
} |
8451
|
|
|
|
8452
|
|
|
/** |
8453
|
|
|
* @param int $lpId |
8454
|
|
|
* @param int $courseId |
8455
|
|
|
* |
8456
|
|
|
* @return mixed |
8457
|
|
|
*/ |
8458
|
|
|
public static function getAccumulateWorkTimePrerequisite($lpId, $courseId) |
8459
|
|
|
{ |
8460
|
|
|
$lpId = (int) $lpId; |
8461
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
8462
|
|
|
$sql = "SELECT accumulate_work_time |
8463
|
|
|
FROM $table |
8464
|
|
|
WHERE iid = $lpId"; |
8465
|
|
|
$result = Database::query($sql); |
8466
|
|
|
$row = Database::fetch_array($result); |
8467
|
|
|
|
8468
|
|
|
return $row['accumulate_work_time']; |
8469
|
|
|
} |
8470
|
|
|
|
8471
|
|
|
/** |
8472
|
|
|
* @param int $courseId |
8473
|
|
|
* |
8474
|
|
|
* @return int |
8475
|
|
|
*/ |
8476
|
|
|
public static function getAccumulateWorkTimeTotal($courseId) |
8477
|
|
|
{ |
8478
|
|
|
$table = Database::get_course_table(TABLE_LP_MAIN); |
8479
|
|
|
$courseId = (int) $courseId; |
8480
|
|
|
$sql = "SELECT SUM(accumulate_work_time) AS total |
8481
|
|
|
FROM $table |
8482
|
|
|
WHERE c_id = $courseId"; |
8483
|
|
|
$result = Database::query($sql); |
8484
|
|
|
$row = Database::fetch_array($result); |
8485
|
|
|
|
8486
|
|
|
return (int) $row['total']; |
8487
|
|
|
} |
8488
|
|
|
|
8489
|
|
|
/** |
8490
|
|
|
* In order to use the lp icon option you need to create the "lp_icon" LP extra field |
8491
|
|
|
* and put the images in. |
8492
|
|
|
*/ |
8493
|
|
|
public static function getIconSelect(): array |
8494
|
|
|
{ |
8495
|
|
|
$theme = Container::$container->get(ThemeHelper::class)->getVisualTheme(); |
8496
|
|
|
$filesystem = Container::$container->get('oneup_flysystem.themes_filesystem'); |
8497
|
|
|
|
8498
|
|
|
if (!$filesystem->directoryExists("$theme/lp_icons")) { |
8499
|
|
|
return []; |
8500
|
|
|
} |
8501
|
|
|
|
8502
|
|
|
$icons = ['' => get_lang('Please select an option')]; |
8503
|
|
|
|
8504
|
|
|
$iconFiles = $filesystem->listContents("$theme/lp_icons"); |
8505
|
|
|
$allowedExtensions = ['image/jpeg', 'image/jpg', 'image/png']; |
8506
|
|
|
|
8507
|
|
|
foreach ($iconFiles as $iconFile) { |
8508
|
|
|
$mimeType = $filesystem->mimeType($iconFile->path()); |
8509
|
|
|
|
8510
|
|
|
if (in_array($mimeType, $allowedExtensions)) { |
8511
|
|
|
$basename = basename($iconFile->path()); |
8512
|
|
|
$icons[$basename] = $basename; |
8513
|
|
|
} |
8514
|
|
|
} |
8515
|
|
|
|
8516
|
|
|
return $icons; |
8517
|
|
|
} |
8518
|
|
|
|
8519
|
|
|
/** |
8520
|
|
|
* @param int $lpId |
8521
|
|
|
* |
8522
|
|
|
* @return string |
8523
|
|
|
*/ |
8524
|
|
|
public static function getSelectedIcon($lpId) |
8525
|
|
|
{ |
8526
|
|
|
$extraFieldValue = new ExtraFieldValue('lp'); |
8527
|
|
|
$lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon'); |
8528
|
|
|
$icon = ''; |
8529
|
|
|
if (!empty($lpIcon) && isset($lpIcon['value'])) { |
8530
|
|
|
$icon = $lpIcon['value']; |
8531
|
|
|
} |
8532
|
|
|
|
8533
|
|
|
return $icon; |
8534
|
|
|
} |
8535
|
|
|
|
8536
|
|
|
public static function getSelectedIconHtml(int $lpId): string |
8537
|
|
|
{ |
8538
|
|
|
$icon = self::getSelectedIcon($lpId); |
8539
|
|
|
|
8540
|
|
|
if (empty($icon)) { |
8541
|
|
|
return ''; |
8542
|
|
|
} |
8543
|
|
|
|
8544
|
|
|
$path = Container::getThemeHelper()->getThemeAssetUrl("lp_icons/$icon"); |
8545
|
|
|
|
8546
|
|
|
return Display::img($path); |
8547
|
|
|
} |
8548
|
|
|
|
8549
|
|
|
/** |
8550
|
|
|
* @param string $value |
8551
|
|
|
* |
8552
|
|
|
* @return string |
8553
|
|
|
*/ |
8554
|
|
|
public function cleanItemTitle($value) |
8555
|
|
|
{ |
8556
|
|
|
$value = Security::remove_XSS(strip_tags($value)); |
8557
|
|
|
|
8558
|
|
|
return $value; |
8559
|
|
|
} |
8560
|
|
|
|
8561
|
|
|
public function setItemTitle(FormValidator $form) |
8562
|
|
|
{ |
8563
|
|
|
if ('true' === api_get_setting('editor.save_titles_as_html')) { |
8564
|
|
|
$form->addHtmlEditor( |
8565
|
|
|
'title', |
8566
|
|
|
get_lang('Title'), |
8567
|
|
|
true, |
8568
|
|
|
false, |
8569
|
|
|
['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')] |
8570
|
|
|
); |
8571
|
|
|
} else { |
8572
|
|
|
$form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']); |
8573
|
|
|
$form->applyFilter('title', 'trim'); |
8574
|
|
|
$form->applyFilter('title', 'html_filter'); |
8575
|
|
|
} |
8576
|
|
|
} |
8577
|
|
|
|
8578
|
|
|
/** |
8579
|
|
|
* @return array |
8580
|
|
|
*/ |
8581
|
|
|
public function getItemsForForm($addParentCondition = false) |
8582
|
|
|
{ |
8583
|
|
|
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); |
8584
|
|
|
|
8585
|
|
|
$sql = "SELECT * FROM $tbl_lp_item |
8586
|
|
|
WHERE path <> 'root' AND lp_id = ".$this->lp_id; |
8587
|
|
|
|
8588
|
|
|
if ($addParentCondition) { |
8589
|
|
|
$sql .= ' AND parent_item_id IS NULL '; |
8590
|
|
|
} |
8591
|
|
|
$sql .= ' ORDER BY display_order ASC'; |
8592
|
|
|
|
8593
|
|
|
$result = Database::query($sql); |
8594
|
|
|
$arrLP = []; |
8595
|
|
|
while ($row = Database::fetch_array($result)) { |
8596
|
|
|
$arrLP[] = [ |
8597
|
|
|
'iid' => $row['iid'], |
8598
|
|
|
'id' => $row['iid'], |
8599
|
|
|
'item_type' => $row['item_type'], |
8600
|
|
|
'title' => $this->cleanItemTitle($row['title']), |
8601
|
|
|
'title_raw' => $row['title'], |
8602
|
|
|
'path' => $row['path'], |
8603
|
|
|
'description' => Security::remove_XSS($row['description']), |
8604
|
|
|
'parent_item_id' => $row['parent_item_id'], |
8605
|
|
|
'previous_item_id' => $row['previous_item_id'], |
8606
|
|
|
'next_item_id' => $row['next_item_id'], |
8607
|
|
|
'display_order' => $row['display_order'], |
8608
|
|
|
'max_score' => $row['max_score'], |
8609
|
|
|
'min_score' => $row['min_score'], |
8610
|
|
|
'mastery_score' => $row['mastery_score'], |
8611
|
|
|
'prerequisite' => $row['prerequisite'], |
8612
|
|
|
'max_time_allowed' => $row['max_time_allowed'], |
8613
|
|
|
'prerequisite_min_score' => $row['prerequisite_min_score'], |
8614
|
|
|
'prerequisite_max_score' => $row['prerequisite_max_score'], |
8615
|
|
|
]; |
8616
|
|
|
} |
8617
|
|
|
|
8618
|
|
|
return $arrLP; |
8619
|
|
|
} |
8620
|
|
|
|
8621
|
|
|
/** |
8622
|
|
|
* Gets whether this SCORM learning path has been marked to use the score |
8623
|
|
|
* as progress. Takes into account whether the learnpath matches (SCORM |
8624
|
|
|
* content + less than 2 items). |
8625
|
|
|
* |
8626
|
|
|
* @return bool True if the score should be used as progress, false otherwise |
8627
|
|
|
*/ |
8628
|
|
|
public function getUseScoreAsProgress() |
8629
|
|
|
{ |
8630
|
|
|
// If not a SCORM, we don't care about the setting |
8631
|
|
|
if (2 != $this->get_type()) { |
8632
|
|
|
return false; |
8633
|
|
|
} |
8634
|
|
|
// If more than one step in the SCORM, we don't care about the setting |
8635
|
|
|
if ($this->get_total_items_count() > 1) { |
8636
|
|
|
return false; |
8637
|
|
|
} |
8638
|
|
|
$extraFieldValue = new ExtraFieldValue('lp'); |
8639
|
|
|
$doUseScore = false; |
8640
|
|
|
$useScore = $extraFieldValue->get_values_by_handler_and_field_variable( |
8641
|
|
|
$this->get_id(), |
8642
|
|
|
'use_score_as_progress' |
8643
|
|
|
); |
8644
|
|
|
if (!empty($useScore) && isset($useScore['value'])) { |
8645
|
|
|
$doUseScore = $useScore['value']; |
8646
|
|
|
} |
8647
|
|
|
|
8648
|
|
|
return $doUseScore; |
8649
|
|
|
} |
8650
|
|
|
|
8651
|
|
|
/** |
8652
|
|
|
* Get the user identifier (user_id or username |
8653
|
|
|
* Depends on scorm_api_username_as_student_id in app/config/configuration.php. |
8654
|
|
|
* |
8655
|
|
|
* @return string User ID or username, depending on configuration setting |
8656
|
|
|
*/ |
8657
|
|
|
public static function getUserIdentifierForExternalServices() |
8658
|
|
|
{ |
8659
|
|
|
$scormApiExtraFieldUseStudentId = api_get_setting('lp.scorm_api_extrafield_to_use_as_student_id'); |
8660
|
|
|
$extraFieldValue = new ExtraFieldValue('user'); |
8661
|
|
|
$extrafield = $extraFieldValue->get_values_by_handler_and_field_variable( |
8662
|
|
|
api_get_user_id(), |
8663
|
|
|
$scormApiExtraFieldUseStudentId |
8664
|
|
|
); |
8665
|
|
|
if (is_array($extrafield) && isset($extrafield['value'])) { |
8666
|
|
|
return $extrafield['value']; |
8667
|
|
|
} else { |
8668
|
|
|
if ('true' === $scormApiExtraFieldUseStudentId) { |
8669
|
|
|
return api_get_user_info(api_get_user_id())['username']; |
8670
|
|
|
} else { |
8671
|
|
|
return api_get_user_id(); |
8672
|
|
|
} |
8673
|
|
|
} |
8674
|
|
|
} |
8675
|
|
|
|
8676
|
|
|
/** |
8677
|
|
|
* Save the new order for learning path items. |
8678
|
|
|
* |
8679
|
|
|
* @param array $orderList A associative array with id and parent_id keys. |
8680
|
|
|
*/ |
8681
|
|
|
public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true, $lpItemRepo = null, $em = null) |
8682
|
|
|
{ |
8683
|
|
|
if (empty($orderList)) { |
8684
|
|
|
return true; |
8685
|
|
|
} |
8686
|
|
|
if (!isset($lpItemRepo)) { |
8687
|
|
|
$lpItemRepo = Container::getLpItemRepository(); |
8688
|
|
|
} |
8689
|
|
|
if (!isset($em)) { |
8690
|
|
|
$em = Database::getManager(); |
8691
|
|
|
} |
8692
|
|
|
$counter = 2; |
8693
|
|
|
$rootItem->setDisplayOrder(1); |
8694
|
|
|
$rootItem->setPreviousItemId(null); |
8695
|
|
|
$em->persist($rootItem); |
8696
|
|
|
if ($flush) { |
8697
|
|
|
$em->flush(); |
8698
|
|
|
} |
8699
|
|
|
|
8700
|
|
|
foreach ($orderList as $item) { |
8701
|
|
|
$itemId = $item->id ?? 0; |
8702
|
|
|
if (empty($itemId)) { |
8703
|
|
|
continue; |
8704
|
|
|
} |
8705
|
|
|
$parentId = $item->parent_id ?? 0; |
8706
|
|
|
$parent = $rootItem; |
8707
|
|
|
if (!empty($parentId)) { |
8708
|
|
|
$parentExists = $lpItemRepo->find($parentId); |
8709
|
|
|
if (null !== $parentExists) { |
8710
|
|
|
$parent = $parentExists; |
8711
|
|
|
} |
8712
|
|
|
} |
8713
|
|
|
|
8714
|
|
|
/** @var CLpItem $itemEntity */ |
8715
|
|
|
$itemEntity = $lpItemRepo->find($itemId); |
8716
|
|
|
$itemEntity->setParent($parent); |
8717
|
|
|
$itemEntity->setPreviousItemId(null); |
8718
|
|
|
$itemEntity->setNextItemId(null); |
8719
|
|
|
$itemEntity->setDisplayOrder($counter); |
8720
|
|
|
|
8721
|
|
|
$em->persist($itemEntity); |
8722
|
|
|
if ($flush) { |
8723
|
|
|
$em->flush(); |
8724
|
|
|
} |
8725
|
|
|
$counter++; |
8726
|
|
|
} |
8727
|
|
|
|
8728
|
|
|
$lpItemRepo->recoverNode($rootItem, 'displayOrder'); |
8729
|
|
|
$em->persist($rootItem); |
8730
|
|
|
if ($flush) { |
8731
|
|
|
$em->flush(); |
8732
|
|
|
} |
8733
|
|
|
|
8734
|
|
|
return true; |
8735
|
|
|
} |
8736
|
|
|
|
8737
|
|
|
public static function move(int $lpId, string $direction) |
8738
|
|
|
{ |
8739
|
|
|
$em = Database::getManager(); |
8740
|
|
|
/** @var CLp $lp */ |
8741
|
|
|
$lp = Container::getLpRepository()->find($lpId); |
8742
|
|
|
if ($lp) { |
8743
|
|
|
$course = api_get_course_entity(); |
8744
|
|
|
$session = api_get_session_entity(); |
8745
|
|
|
$group = api_get_group_entity(); |
8746
|
|
|
|
8747
|
|
|
$link = $lp->getResourceNode()->getResourceLinkByContext($course, $session, $group); |
8748
|
|
|
|
8749
|
|
|
if ($link) { |
8750
|
|
|
if ('down' === $direction) { |
8751
|
|
|
$link->moveDownPosition(); |
8752
|
|
|
} |
8753
|
|
|
if ('up' === $direction) { |
8754
|
|
|
$link->moveUpPosition(); |
8755
|
|
|
} |
8756
|
|
|
|
8757
|
|
|
$em->flush(); |
8758
|
|
|
} |
8759
|
|
|
} |
8760
|
|
|
} |
8761
|
|
|
|
8762
|
|
|
/** |
8763
|
|
|
* Get the depth level of LP item. |
8764
|
|
|
* |
8765
|
|
|
* @param array $items |
8766
|
|
|
* @param int $currentItemId |
8767
|
|
|
* |
8768
|
|
|
* @return int |
8769
|
|
|
*/ |
8770
|
|
|
private static function get_level_for_item($items, $currentItemId) |
8771
|
|
|
{ |
8772
|
|
|
$parentItemId = 0; |
8773
|
|
|
if (isset($items[$currentItemId])) { |
8774
|
|
|
$parentItemId = $items[$currentItemId]->parent; |
8775
|
|
|
} |
8776
|
|
|
|
8777
|
|
|
if (0 == $parentItemId) { |
8778
|
|
|
return 0; |
8779
|
|
|
} |
8780
|
|
|
|
8781
|
|
|
return self::get_level_for_item($items, $parentItemId) + 1; |
8782
|
|
|
} |
8783
|
|
|
|
8784
|
|
|
/** |
8785
|
|
|
* Generate the link for a learnpath category as course tool. |
8786
|
|
|
* |
8787
|
|
|
* @param int $categoryId |
8788
|
|
|
* |
8789
|
|
|
* @return string |
8790
|
|
|
*/ |
8791
|
|
|
private static function getCategoryLinkForTool($categoryId) |
8792
|
|
|
{ |
8793
|
|
|
$categoryId = (int) $categoryId; |
8794
|
|
|
return 'lp/lp_controller.php?'.api_get_cidreq().'&' |
8795
|
|
|
.http_build_query( |
8796
|
|
|
[ |
8797
|
|
|
'action' => 'view_category', |
8798
|
|
|
'id' => $categoryId, |
8799
|
|
|
] |
8800
|
|
|
); |
8801
|
|
|
} |
8802
|
|
|
|
8803
|
|
|
/** |
8804
|
|
|
* Check and obtain the lp final item if exist. |
8805
|
|
|
* |
8806
|
|
|
* @return learnpathItem |
8807
|
|
|
*/ |
8808
|
|
|
private function getFinalItem() |
8809
|
|
|
{ |
8810
|
|
|
if (empty($this->items)) { |
8811
|
|
|
return null; |
8812
|
|
|
} |
8813
|
|
|
|
8814
|
|
|
foreach ($this->items as $item) { |
8815
|
|
|
if ('final_item' !== $item->type) { |
8816
|
|
|
continue; |
8817
|
|
|
} |
8818
|
|
|
|
8819
|
|
|
return $item; |
8820
|
|
|
} |
8821
|
|
|
} |
8822
|
|
|
|
8823
|
|
|
/** |
8824
|
|
|
* Get the LP Final Item Template. |
8825
|
|
|
* |
8826
|
|
|
* @return string |
8827
|
|
|
*/ |
8828
|
|
|
private function getFinalItemTemplate() |
8829
|
|
|
{ |
8830
|
|
|
return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html'); |
8831
|
|
|
} |
8832
|
|
|
|
8833
|
|
|
/** |
8834
|
|
|
* Get the LP Final Item Url. |
8835
|
|
|
* |
8836
|
|
|
* @return string |
8837
|
|
|
*/ |
8838
|
|
|
private function getSavedFinalItem() |
8839
|
|
|
{ |
8840
|
|
|
$finalItem = $this->getFinalItem(); |
8841
|
|
|
|
8842
|
|
|
$repo = Container::getDocumentRepository(); |
8843
|
|
|
/** @var CDocument $document */ |
8844
|
|
|
$document = $repo->find($finalItem->path); |
8845
|
|
|
|
8846
|
|
|
return $document ? $repo->getResourceFileContent($document) : ''; |
8847
|
|
|
} |
8848
|
|
|
|
8849
|
|
|
/** |
8850
|
|
|
* Recalculates the results for all exercises associated with the learning path (LP) for the given user. |
8851
|
|
|
*/ |
8852
|
|
|
public function recalculateResultsForLp(int $userId): void |
8853
|
|
|
{ |
8854
|
|
|
$em = Database::getManager(); |
8855
|
|
|
$lpItemRepo = $em->getRepository(CLpItem::class); |
8856
|
|
|
$lpItems = $lpItemRepo->findBy(['lp' => $this->lp_id]); |
8857
|
|
|
|
8858
|
|
|
if (empty($lpItems)) { |
8859
|
|
|
Display::addFlash(Display::return_message(get_lang('No items found'), 'error')); |
8860
|
|
|
return; |
8861
|
|
|
} |
8862
|
|
|
|
8863
|
|
|
$lpItemsById = []; |
8864
|
|
|
foreach ($lpItems as $item) { |
8865
|
|
|
$lpItemsById[$item->getIid()] = $item; |
8866
|
|
|
} |
8867
|
|
|
|
8868
|
|
|
$trackEExerciseRepo = $em->getRepository(TrackEExercise::class); |
8869
|
|
|
$trackExercises = $trackEExerciseRepo->createQueryBuilder('te') |
8870
|
|
|
->where('te.origLpId = :lpId') |
8871
|
|
|
->andWhere('te.user = :userId') |
8872
|
|
|
->andWhere('te.origLpItemId IN (:lpItemIds)') |
8873
|
|
|
->setParameter('lpId', $this->lp_id) |
8874
|
|
|
->setParameter('userId', $userId) |
8875
|
|
|
->setParameter('lpItemIds', array_keys($lpItemsById)) |
8876
|
|
|
->getQuery() |
8877
|
|
|
->getResult(); |
8878
|
|
|
|
8879
|
|
|
if (empty($trackExercises)) { |
8880
|
|
|
Display::addFlash(Display::return_message(get_lang('No exercise attempts found'), 'error')); |
8881
|
|
|
return; |
8882
|
|
|
} |
8883
|
|
|
|
8884
|
|
|
foreach ($trackExercises as $trackExercise) { |
8885
|
|
|
$exeId = $trackExercise->getExeId(); |
8886
|
|
|
$lpItemId = $trackExercise->getOrigLpItemId(); |
8887
|
|
|
|
8888
|
|
|
if (!isset($lpItemsById[$lpItemId])) { |
8889
|
|
|
continue; |
8890
|
|
|
} |
8891
|
|
|
|
8892
|
|
|
$lpItem = $lpItemsById[$lpItemId]; |
8893
|
|
|
if ('quiz' !== $lpItem->getItemType()) { |
8894
|
|
|
continue; |
8895
|
|
|
} |
8896
|
|
|
|
8897
|
|
|
$quizId = (int) $lpItem->getPath(); |
8898
|
|
|
$courseId = (int) $trackExercise->getCourse()->getId(); |
8899
|
|
|
$updatedExercise = ExerciseLib::recalculateResult($exeId, $userId, $quizId, $courseId); |
8900
|
|
|
if ($updatedExercise instanceof TrackEExercise) { |
8901
|
|
|
Display::addFlash(Display::return_message(get_lang('Results recalculated'), 'success')); |
8902
|
|
|
} else { |
8903
|
|
|
Display::addFlash(Display::return_message(get_lang('Error recalculating results'), 'error')); |
8904
|
|
|
} |
8905
|
|
|
} |
8906
|
|
|
} |
8907
|
|
|
} |
8908
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.