1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
|
<?php
/**
* vips/pool.php - assignment pool controller
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* @author Elmar Ludwig
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
*/
class Vips_PoolController extends AuthenticatedController
{
/**
* Callback function being called before an action is executed. If this
* function does not return FALSE, the action will be called, otherwise
* an error will be generated and processing will be aborted. If this function
* already #rendered or #redirected, further processing of the action is
* withheld.
*
* @param string Name of the action to perform.
* @param array An array of arguments to the action.
*
* @return bool|void
*/
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
PageLayout::setHelpKeyword('Basis.Vips');
}
/**
* Display all exercises that are available for this user.
* Available in this case means the exercise is in a course where the user
* is at least tutor.
* Lecturer/tutor can select which exercise to edit/assign/delete.
*/
public function exercises_action()
{
Navigation::activateItem('/contents/vips/exercises');
PageLayout::setTitle(_('Meine Aufgaben'));
Helpbar::get()->addPlainText('',
_('Auf dieser Seite finden Sie eine Übersicht über alle Aufgaben, auf die Sie Zugriff haben.'));
$range_type = $_SESSION['view_context'] ?? 'user';
$range_type = Request::option('range_type', $range_type);
$_SESSION['view_context'] = $range_type;
$widget = new ViewsWidget();
$widget->addLink(
_('Persönliche Aufgabensammlung'),
$this->url_for('vips/pool/exercises', ['range_type' => 'user'])
)->setActive($range_type === 'user');
$widget->addLink(
_('Aufgaben in Veranstaltungen'),
$this->url_for('vips/pool/exercises', ['range_type' => 'course'])
)->setActive($range_type === 'course');
Sidebar::get()->addWidget($widget);
$sort = Request::option('sort', 'mkdate');
$desc = Request::int('desc', $sort === 'mkdate');
$page = Request::int('page', 1);
$size = Config::get()->ENTRIES_PER_PAGE;
$search_filter = Request::getArray('search_filter') + ['search_string' => '', 'exercise_type' => ''];
$search_filter['search_string'] = Request::get('pool_search_parameter', $search_filter['search_string']);
$search_filter['exercise_type'] = Request::get('exercise_type', $search_filter['exercise_type']);
if (Request::submitted('start_search') || Request::int('pool_search')) {
$search_filter = [
'search_string' => Request::get('pool_search_parameter'),
'exercise_type' => Request::get('exercise_type')
];
} else if (empty($search_filter) || Request::submitted('reset_search')) {
$search_filter = array_fill_keys(['search_string', 'exercise_type'], '');
}
// get exercises of this user and where he/she has permission
if ($range_type === 'course') {
$course_ids = array_column(VipsModule::getActiveCourses($GLOBALS['user']->id), 'id');
} else {
$course_ids = [$GLOBALS['user']->id];
}
// set up the sql query for the quicksearch
$sql = "SELECT etask_tasks.id, etask_tasks.title FROM etask_tasks
JOIN etask_test_tasks ON etask_tasks.id = etask_test_tasks.task_id
JOIN etask_assignments USING (test_id)
WHERE etask_assignments.range_id IN ('" . implode("','", $course_ids) . "')
AND etask_assignments.type IN ('exam', 'practice', 'selftest')
AND (etask_tasks.title LIKE :input OR etask_tasks.description LIKE :input)
AND IF(:exercise_type = '', 1, etask_tasks.type = :exercise_type)
ORDER BY title";
$search = new SQLSearch($sql, _('Titel der Aufgabe'));
$widget = new VipsSearchWidget($this->url_for('vips/pool/exercises', ['exercise_type' => $search_filter['exercise_type']]));
$widget->addNeedle(_('Suche'), 'pool_search', true, $search, 'function(id, name) { this.value = name; this.form.submit(); }', $search_filter['search_string']);
Sidebar::get()->addWidget($widget);
$widget = new SelectWidget(_('Aufgabentyp'), $this->url_for('vips/pool/exercises', ['pool_search_parameter' => $search_filter['search_string']]), 'exercise_type');
$element = new SelectElement('', _('Alle Aufgabentypen'));
$widget->addElement($element);
Sidebar::get()->addWidget($widget);
foreach (Exercise::getExerciseTypes() as $type => $entry) {
$element = new SelectElement($type, $entry['name'], $type === $search_filter['exercise_type']);
$widget->addElement($element);
}
$result = $this->getAllExercises($course_ids, $sort, $desc, $search_filter);
$this->sort = $sort;
$this->desc = $desc;
$this->page = $page;
$this->count = count($result);
$this->exercises = array_slice($result, $size * ($page - 1), $size);
$this->search_filter = $search_filter;
}
/**
* Display all assignments that are available for this user.
* Available in this case means the assignment is in a course where the user
* is at least tutor.
* Lecturer/tutor can select which assignment to edit/delete.
*/
public function assignments_action()
{
Navigation::activateItem('/contents/vips/assignments');
PageLayout::setTitle(_('Meine Aufgabenblätter'));
Helpbar::get()->addPlainText('',
_('Auf dieser Seite finden Sie eine Übersicht über alle Aufgabenblätter, auf die Sie Zugriff haben.'));
$range_type = $_SESSION['view_context'] ?? 'user';
$range_type = Request::option('range_type', $range_type);
$_SESSION['view_context'] = $range_type;
$widget = new ActionsWidget();
$widget->addLink(
_('Aufgabenblatt erstellen'),
$this->url_for('vips/sheets/edit_assignment'),
Icon::create('add')
);
$widget->addLink(
_('Aufgabenblatt kopieren'),
$this->url_for('vips/sheets/copy_assignment_dialog'),
Icon::create('copy')
)->asDialog('size=1200x800');
$widget->addLink(
_('Aufgabenblatt importieren'),
$this->url_for('vips/sheets/import_assignment_dialog'),
Icon::create('import')
)->asDialog('size=auto');
Sidebar::get()->addWidget($widget);
$widget = new ViewsWidget();
$widget->addLink(
_('Persönliche Aufgabensammlung'),
$this->url_for('vips/pool/assignments', ['range_type' => 'user'])
)->setActive($range_type === 'user');
$widget->addLink(
_('Aufgaben in Veranstaltungen'),
$this->url_for('vips/pool/assignments', ['range_type' => 'course'])
)->setActive($range_type === 'course');
Sidebar::get()->addWidget($widget);
$sort = Request::option('sort', 'mkdate');
$desc = Request::int('desc', $sort === 'mkdate');
$page = Request::int('page', 1);
$size = Config::get()->ENTRIES_PER_PAGE;
$search_filter = Request::getArray('search_filter') + ['search_string' => '', 'assignment_type' => ''];
$search_filter['search_string'] = Request::get('pool_search_parameter', $search_filter['search_string']);
$search_filter['assignment_type'] = Request::get('assignment_type', $search_filter['assignment_type']);
// get assignments of this user and where he/she has permission
if ($range_type === 'course') {
$course_ids = array_column(VipsModule::getActiveCourses($GLOBALS['user']->id), 'id');
} else {
$course_ids = [$GLOBALS['user']->id];
}
// set up the sql query for the quicksearch
$sql = "SELECT etask_assignments.id, etask_tests.title FROM etask_tests
JOIN etask_assignments ON etask_tests.id = etask_assignments.test_id
WHERE etask_assignments.range_id IN ('" . implode("','", $course_ids) . "')
AND etask_assignments.type IN ('exam', 'practice', 'selftest')
AND (etask_tests.title LIKE :input OR etask_tests.description LIKE :input)
AND IF(:assignment_type = '', 1, etask_assignments.type = :assignment_type)
ORDER BY title";
$search = new SQLSearch($sql, _('Titel des Aufgabenblatts'));
$widget = new VipsSearchWidget($this->url_for('vips/pool/assignments', ['assignment_type' => $search_filter['assignment_type']]));
$widget->addNeedle(_('Suche'), 'pool_search', true, $search, 'function(id, name) { this.value = name; this.form.submit(); }', $search_filter['search_string']);
Sidebar::get()->addWidget($widget);
$widget = new SelectWidget(_('Modus'), $this->url_for('vips/pool/assignments', ['pool_search_parameter' => $search_filter['search_string']]), 'assignment_type');
$element = new SelectElement('', _('Beliebiger Modus'));
$widget->addElement($element);
Sidebar::get()->addWidget($widget);
foreach (VipsAssignment::getAssignmentTypes() as $type => $entry) {
$element = new SelectElement($type, $entry['name'], $type === $search_filter['assignment_type']);
$widget->addElement($element);
}
$result = $this->getAllAssignments($course_ids, $sort, $desc, $search_filter);
$this->sort = $sort;
$this->desc = $desc;
$this->page = $page;
$this->count = count($result);
$this->assignments = array_slice($result, $size * ($page - 1), $size);
$this->search_filter = $search_filter;
}
/**
* Get all matching exercises from a list of courses in given order.
* If $search_filter is not empty, search filters are applied.
*
* @param course_ids list of courses to get exercises from
* @param sort sort exercise list by this property
* @param desc true if sort direction is descending
* @param search_filter the currently active search filter
*
* @return array with data of all matching exercises
*/
public function getAllExercises($course_ids, $sort, $desc, $search_filter)
{
$db = DBManager::get();
// check if some filters are active
$search_string = $search_filter['search_string'];
$exercise_type = $search_filter['exercise_type'];
$sql = "SELECT etask_tasks.*,
auth_user_md5.Nachname,
auth_user_md5.Vorname,
etask_assignments.id AS assignment_id,
etask_assignments.range_id,
etask_assignments.range_type,
etask_tests.title AS test_title
FROM etask_tasks
LEFT JOIN auth_user_md5 USING(user_id)
JOIN etask_test_tasks ON etask_tasks.id = etask_test_tasks.task_id
JOIN etask_tests ON etask_tests.id = etask_test_tasks.test_id
JOIN etask_assignments USING (test_id)
WHERE etask_assignments.range_id IN (:course_ids)
AND etask_assignments.type IN ('exam', 'practice', 'selftest') " .
($search_string ? 'AND (etask_tasks.title LIKE :input OR
etask_tasks.description LIKE :input) ' : '') .
($exercise_type ? 'AND etask_tasks.type = :exercise_type ' : '') .
"ORDER BY :sort :desc, title";
$stmt = $db->prepare($sql);
$stmt->bindValue(':course_ids', $course_ids);
$stmt->bindValue(':input', '%' . $search_string . '%');
$stmt->bindValue(':exercise_type', $exercise_type);
$stmt->bindValue(':sort', $sort, StudipPDO::PARAM_COLUMN);
$stmt->bindValue(':desc', $desc ? 'DESC' : 'ASC', StudipPDO::PARAM_COLUMN);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get all matching assignments from a list of courses in given order.
* If $search_filter is not empty, search filters are applied.
*
* @param course_ids list of courses to get assignments from
* @param sort sort assignment list by this property
* @param desc true if sort direction is descending
* @param search_filter the currently active search filter
*
* @return array with data of all matching assignments
*/
public function getAllAssignments($course_ids, $sort, $desc, $search_filter)
{
$db = DBManager::get();
// check if some filters are active
$search_string = $search_filter['search_string'];
$assignment_type = $search_filter['assignment_type'];
$types = $assignment_type ? [$assignment_type] : ['exam', 'practice', 'selftest'];
$sql = "SELECT etask_assignments.*,
etask_tests.title AS test_title,
auth_user_md5.Nachname,
auth_user_md5.Vorname,
seminare.Name,
(SELECT MIN(beginn) FROM semester_data
JOIN semester_courses USING(semester_id)
WHERE course_id = Seminar_id) AS start_time
FROM etask_tests
LEFT JOIN auth_user_md5 USING(user_id)
JOIN etask_assignments ON etask_tests.id = etask_assignments.test_id
LEFT JOIN seminare ON etask_assignments.range_id = seminare.Seminar_id
WHERE etask_assignments.range_id IN (:course_ids)
AND etask_assignments.type IN (:types) " .
($search_string ? 'AND (etask_tests.title LIKE :input OR
etask_tests.description LIKE :input) ' : '') .
"ORDER BY :sort :desc, title";
$stmt = $db->prepare($sql);
$stmt->bindValue(':course_ids', $course_ids);
$stmt->bindValue(':input', '%' . $search_string . '%');
$stmt->bindValue(':types', $types);
$stmt->bindValue(':sort', $sort, StudipPDO::PARAM_COLUMN);
$stmt->bindValue(':desc', $desc ? 'DESC' : 'ASC', StudipPDO::PARAM_COLUMN);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Delete a list of exercises from their respective assignments.
*/
public function delete_exercises_action()
{
CSRFProtection::verifyUnsafeRequest();
$exercise_ids = Request::intArray('exercise_ids');
$deleted = 0;
foreach ($exercise_ids as $exercise_id => $assignment_id) {
$assignment = VipsAssignment::find($assignment_id);
VipsModule::requireEditPermission($assignment, $exercise_id);
if (!$assignment->isLocked()) {
$assignment->test->removeExercise($exercise_id);
++$deleted;
}
}
if ($deleted > 0) {
PageLayout::postSuccess(sprintf(ngettext('Die Aufgabe wurde gelöscht.', 'Es wurden %s Aufgaben gelöscht.', $deleted), $deleted));
}
if ($deleted < count($exercise_ids)) {
PageLayout::postError(_('Einige Aufgaben konnten nicht gelöscht werden, da die Aufgabenblätter gesperrt sind.'), [
_('Falls Sie diese wirklich löschen möchten, müssen Sie zuerst die Lösungen aller Teilnehmenden zurücksetzen.')
]);
}
$this->redirect('vips/pool/exercises');
}
/**
* Dialog for copying a list of exercises into an existing assignment.
*/
public function copy_exercises_dialog_action()
{
PageLayout::setTitle(_('Aufgaben in vorhandenes Aufgabenblatt kopieren'));
$this->exercise_ids = Request::intArray('exercise_ids');
$this->courses = VipsModule::getActiveCourses($GLOBALS['user']->id);
}
/**
* Copy the selected exercises into the selected assignment.
*/
public function copy_exercises_action()
{
CSRFProtection::verifyUnsafeRequest();
$exercise_ids = Request::intArray('exercise_ids');
$target_assignment_id = Request::int('assignment_id');
$target_assignment = VipsAssignment::find($target_assignment_id);
VipsModule::requireEditPermission($target_assignment);
if (!$target_assignment->isLocked()) {
foreach ($exercise_ids as $exercise_id => $assignment_id) {
$assignment = VipsAssignment::find($assignment_id);
VipsModule::requireEditPermission($assignment);
$exercise_ref = VipsExerciseRef::find([$assignment->test_id, $exercise_id]);
$exercise_ref->copyIntoTest($target_assignment->test_id);
}
PageLayout::postSuccess(ngettext('Die Aufgabe wurde kopiert.', 'Die Aufgaben wurden kopiert.', count($exercise_ids)));
}
$this->redirect('vips/pool/exercises');
}
/**
* Dialog for moving a list of exercises into an existing assignment.
*/
public function move_exercises_dialog_action()
{
PageLayout::setTitle(_('Aufgaben in vorhandenes Aufgabenblatt verschieben'));
$this->exercise_ids = Request::intArray('exercise_ids');
$this->courses = VipsModule::getActiveCourses($GLOBALS['user']->id);
}
/**
* Move the selected exercises into the selected assignment.
*/
public function move_exercises_action()
{
CSRFProtection::verifyUnsafeRequest();
$exercise_ids = Request::intArray('exercise_ids');
$target_assignment_id = Request::int('assignment_id');
$target_assignment = VipsAssignment::find($target_assignment_id);
$moved = 0;
VipsModule::requireEditPermission($target_assignment);
if (!$target_assignment->isLocked()) {
foreach ($exercise_ids as $exercise_id => $assignment_id) {
$assignment = VipsAssignment::find($assignment_id);
VipsModule::requireEditPermission($assignment);
if (!$assignment->isLocked()) {
$exercise_ref = VipsExerciseRef::find([$assignment->test_id, $exercise_id]);
$exercise_ref->moveIntoTest($target_assignment->test_id);
++$moved;
}
}
}
if ($moved > 0) {
PageLayout::postSuccess(sprintf(ngettext('Die Aufgabe wurde verschoben.', 'Es wurden %s Aufgaben verschoben.', $moved), $moved));
}
if ($moved < count($exercise_ids)) {
PageLayout::postError(_('Einige Aufgaben konnten nicht verschoben werden, da die Aufgabenblätter gesperrt sind.'));
}
$this->redirect('vips/pool/exercises');
}
/**
* Return the appropriate CSS class for sortable column (if any).
*
* @param boolean $sort sort by this column
* @param boolean $desc set sort direction
*/
public function sort_class(bool $sort, ?bool $desc): string
{
return $sort ? ($desc ? 'sortdesc' : 'sortasc') : '';
}
/**
* Render a generic page chooser selector. The first occurence of '%d'
* in the URL is replaced with the selected page number.
*
* @param string $url URL for one of the pages
* @param string $count total number of entries
* @param string $page current page to display
* @param string|null $dialog Optional dialog attribute content
* @param int|null $page_size page size (defaults to system default)
* @return mixed
*/
function page_chooser(string $url, string $count, string $page, ?string $dialog = null, ?int $page_size = null)
{
$template = $GLOBALS['template_factory']->open('shared/pagechooser');
$template->dialog = $dialog;
$template->num_postings = $count;
$template->page = $page;
$template->perPage = $page_size ?: Config::get()->ENTRIES_PER_PAGE;
$template->pagelink = str_replace('%%25d', '%d', str_replace('%', '%%', $url));
return $template->render();
}
}
|