Newer
Older
<?php
/**
* wizard.php
* Controller for course creation wizard.
*
* 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 Thomas Hackl <thomas.hackl@uni-passau.de>
* @copyright 2015 Stud.IP Core-Group
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @since 3.3
*/
class Course_WizardController extends AuthenticatedController
{
/**
* @var Array steps the wizard has to execute in order to create a new course.
*/
public $steps = [];
public function before_filter (&$action, &$args)
{
parent::before_filter($action, $args);
$this->dialog = Request::isXhr();
$this->studygroup = Request::bool('studygroup', $this->flash['studygroup'] ?? false);
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
if (!$this->studygroup) {
PageLayout::setTitle(_('Neue Veranstaltung anlegen'));
$navigation = new Navigation(_('Neue Veranstaltung anlegen'), 'dispatch.php/course/wizard');
Navigation::addItem('/browse/my_courses/new_course', $navigation);
Navigation::activateItem('/browse/my_courses/new_course');
} else {
$this->flash['studygroup'] = true;
PageLayout::setTitle(_('Neue Studiengruppe anlegen'));
$navigation = new Navigation(_('Neue Studiengruppe anlegen'), 'dispatch.php/course/wizard?studygroup=1');
Navigation::addItem('/browse/my_courses/new_course', $navigation);
Navigation::activateItem('/browse/my_courses/new_course');
}
$this->steps = CourseWizardStepRegistry::findBySQL("`enabled`=1 ORDER BY `number`");
if ($GLOBALS['user']->perms === 'user') {
throw new AccessDeniedException();
}
}
/**
* Just some sort of placeholder for initial calling without a step number.
*/
public function index_action()
{
$this->redirect('course/wizard/step/0' . ($this->studygroup ? '?studygroup=1' : ''));
}
/**
* Fetches the wizard step with the given number and gets the
* corresponding template.
*
* @param int $number step number to show
* @param String $temp_id temporary ID for the course to create
*/
public function step_action($number=0, $temp_id='')
{
$step = $this->getStep($number);
if (!$temp_id) {
$this->initialize();
if (Request::getArray('batchcreate')) {
$_SESSION['coursewizard'][$this->temp_id]['batchcreate'] = Request::getArray('batchcreate');
}
} else {
$this->temp_id = $temp_id;
}
if ($number == 0) {
$this->first_step = true;
}
if ($this->studygroup) {
// Add special studygroup flag to set values.
$this->setStepValues(
get_class($step),
array_merge($this->getValues(get_class($step)), ['studygroup' => 1])
);
}
$this->values = $this->getValues();
$this->content = $step->getStepTemplate($this->values, $number, $this->temp_id);
$this->stepnumber = $number;
}
/**
* Processes a finished wizard step by saving the gathered values to
* session.
* @param int $step_number the step we are at.
* @param String $temp_id temporary ID for the course to create
*/
public function process_action($step_number, $temp_id)
{
$this->temp_id = $temp_id;
// Get request data and store it in session.
$iterator = Request::getInstance()->getIterator();
$values = [];
while ($iterator->valid()) {
$values[$iterator->key()] = $iterator->current();
$iterator->next();
}
if (!empty($this->steps[$step_number]['classname'])) {
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
$this->setStepValues($this->steps[$step_number]['classname'], $values);
}
// Back or forward button clicked -> set next step accordingly.
if (Request::submitted('back')) {
$next_step = $this->getNextRequiredStep($step_number, 'down');
} else if (Request::submitted('next')) {
// Validate given data.
if ($this->getStep($step_number)->validate($this->getValues())) {
$next_step = $this->getNextRequiredStep($step_number, 'up');
/*
* Validation failed -> stay on current step. Error messages are
* provided via the called step class validation method.
*/
} else {
$next_step = $step_number;
}
// The "create" button was clicked -> create course.
} else if (Request::submitted('create')) {
$_SESSION['coursewizard'][$this->temp_id]['copy_basic_data'] = Request::submitted('copy_basic_data');
if ($this->getValues()) {
// Batch creation of several courses at once.
if ($batch = Request::getArray('batchcreate')) {
$numbering = ($batch['numbering'] == 'number' ? 1 : 'A');
$success = 0;
$failed = 0;
// Create given number of courses.
for ($i = 1 ; $i <= $batch['number'] ; $i++) {
if ($newcourse = $this->createCourse($i == $batch['number'] ? true : false)) {
// Add corresponding number/letter to name or number of newly created course.
if ($batch['add_number_to'] == 'name') {
$newcourse->name .= ' ' . $numbering;
} else if ($batch['add_number_to'] == 'number') {
$newcourse->veranstaltungsnummer = $batch['numbering'] == 'number' ?
$numbering : $newcourse->veranstaltungsnummer . ' ' . $numbering;
}
$newcourse->parent_course = $batch['parent'];
if ($newcourse->store()) {
$numbering++;
$success++;
} else {
$failed++;
}
} else {
$failed++;
}
}
// Show message for successfully created courses.
if ($success > 0) {
PageLayout::postSuccess(sprintf(_('%u Veranstaltungen wurden angelegt.'), $success));
}
// Show message for courses that couldn't be created.
if ($failed > 0) {
PageLayout::postError(sprintf(_('%u Veranstaltungen konnten nicht angelegt werden.'), $failed));
}
$this->redirect(URLHelper::getURL('dispatch.php/course/grouping/children',
['cid' => $batch['parent']]));
} else {
$this->course = $this->createCourse();
if ($this->course) {
if (!$GLOBALS['perm']->have_perm('root')) {
$dest_url = 'course/contentmodules';
} else {
$dest_url = 'course/basicdata/view';
}
// A studygroup has been created.
if (in_array($this->course->status, studygroup_sem_types())) {
$message = MessageBox::success(sprintf(
_('Die Studien-/Arbeitsgruppe "%s" wurde angelegt. '
. 'Sie können sie direkt hier weiter verwalten.'),
htmlReady($this->course->name)
));
$target = $this->url_for('course/studygroup/edit', ['cid' => $this->course->id]);
} elseif (Request::int('dialog') && $GLOBALS['perm']->have_perm('admin')) {
$message = MessageBox::success(sprintf(
_('Die Veranstaltung <a class="link-intern" href="%s">"%s"</a> wurde angelegt.'),
$this->link_for($dest_url, ['cid' => $this->course->id]),
htmlReady($this->course->getFullName())
));
$target = $this->url_for('admin/courses');
$message = MessageBox::success(sprintf(
_('Die Veranstaltung "%s" wurde angelegt. Sie können sie direkt hier weiter verwalten.'),
htmlReady($this->course->getFullName())
$target = $this->url_for($dest_url, ['cid' => $this->course->id]);
PageLayout::postMessage($message);
$this->redirect($target);
} else {
PageLayout::postError(_('Die Veranstaltung konnte nicht angelegt werden.'));
$this->redirect('course/wizard');
}
}
} else {
PageLayout::postMessage(MessageBox::error(_('Die angegebene Veranstaltung wurde bereits angelegt.')));
$this->redirect('course/wizard');
}
$stop = true;
/*
* Something other than "back", "next" or "create" was clicked,
* e.g. QuickSearch
* -> stay on current step and process given values.
*/
} else {
$stepclass = $this->steps[$step_number]['classname'];
$result = $this->getStep($step_number)
->alterValues($this->getValues());
$_SESSION['coursewizard'][$temp_id][$stepclass] = $result;
$next_step = $step_number;
}
if (!$stop) {
// We are after the last step -> all done, show summary.
if ($next_step >= count($this->steps)) {
$this->redirect($this->url_for('course/wizard/summary', $next_step, $temp_id));
// Redirect to next step.
} else {
$this->redirect($this->url_for('course/wizard/step', $next_step, $this->temp_id));
}
}
}
/**
* We are after last step: all set and ready to create a new course.
*/
public function summary_action($stepnumber, $temp_id)
{
$this->stepnumber = $stepnumber;
$this->temp_id = $temp_id;
PageLayout::postError(_('Ihre Session ist abgelaufen, bitte erneut anfangen.'));
$this->redirect('course/wizard');
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
}
if (isset($_SESSION['coursewizard'][$this->temp_id]['source_id'])) {
$this->source_course = Course::find($_SESSION['coursewizard'][$this->temp_id]['source_id']);
}
}
/**
* Wrapper for ajax calls to step classes. Three things must be given
* via Request:
* - step number
* - method to call in target step
* - parameters for the target method (will be passed in given order)
*/
public function ajax_action()
{
$stepNumber = Request::int('step');
$method = Request::get('method');
$parameters = Request::getArray('parameter');
$result = call_user_func_array([$this->getStep($stepNumber), $method], $parameters);
if (is_array($result) || is_object($result)) {
$this->render_json($result);
} else {
$this->render_text($result);
}
}
public function forward_action($step_number, $temp_id)
{
$this->temp_id = $temp_id;
$stepclass = $this->steps[$step_number]['classname'];
$result = $this->getStep($step_number)->alterValues($this->getValues() ?: []);
$this->setStepValues($stepclass, $result);
$this->redirect($this->url_for('course/wizard/step', $step_number, $this->temp_id));
}
/**
* Copy an existing course.
*/
public function copy_action($id) {
if (!$GLOBALS['perm']->have_studip_perm('dozent', $id)
|| LockRules::Check($id, 'seminar_copy')) {
throw new AccessDeniedException(_("Sie dürfen diese Veranstaltung nicht kopieren"));
}
$course = Course::find($id);
$values = [];
for ($i = 0 ; $i < sizeof($this->steps) ; $i++) {
$step = $this->getStep($i);
$values = $step->copy($course, $values);
}
$values['source_id'] = $course->id;
$this->initialize();
$_SESSION['coursewizard'][$this->temp_id] = $values;
$this->redirect($this->url_for('course/wizard/step/0/' . $this->temp_id, ['cid' => '']));
}
/**
* Creates a temporary ID for storing the wizard values in session.
*/
private function initialize()
{
$temp_id = md5(uniqid(microtime()));
$_SESSION['coursewizard'][$temp_id] = [];
$this->temp_id = $temp_id;
}
/**
* Wizard finished: we can create the course now. First store an empty,
* invisible course for getting an ID. Then, iterate through steps and
* set values from each step.
* @param bool $cleanup cleanup session after course creation?
* @return Course
* @throws Exception
*/
private function createCourse($cleanup = true)
{
foreach (array_keys($this->steps) as $n) {
$step = $this->getStep($n);
if ($step->isRequired($this->getValues())) {
if (!$step->validate($this->getValues())) {
unset($_SESSION['coursewizard'][$this->temp_id]);
return false;
}
}
}
// Create a new (empty) course so that we get an ID.
$course = new Course();
$course->visible = 0;
$course->setId($course->getNewId());
$course_id = $course->id;
// Each (required) step stores its own values at the course object.
for ($i = 0; $i < sizeof($this->steps) ; $i++) {
$step = $this->getStep($i);
if ($step->isRequired($this->getValues())) {
if ($stored = $step->storeValues($course, $this->getValues())) {
$course = $stored;
} else {
$course = false;
unset($_SESSION['coursewizard'][$this->temp_id]);
break;
//throw new Exception(_('Die Daten aus Schritt ' . $i . ' konnten nicht gespeichert werden, breche ab.'));
}
}
}
// Cleanup session data if necessary.
if ($cleanup) {
unset($_SESSION['coursewizard'][$this->temp_id]);
}
return $course;
}
/**
* Fetches the class belonging to the wizard step at the given index.
* @param $number
* @return mixed
*/
private function getStep($number)
{
$classname = $this->steps[$number]['classname'];
return new $classname();
}
/**
* Not all steps are required for each course type, some sem_classes must
* not have study areas, for example. So we need to check which step is
* required next, starting from an index and going up or down, according
* to navigation through the wizard.
* @param $number
* @param string $direction
* @return mixed
*/
private function getNextRequiredStep($number, $direction='up')
{
$found = false;
switch ($direction) {
case 'up':
$i = $number + 1;
while (!$found && $i < sizeof($this->steps)) {
$step = $this->getStep($i);
if ($step->isRequired($this->getValues())) {
$found = true;
} else {
$i++;
}
}
break;
case 'down':
$i = $number - 1;
while (!$found && $i >= 0) {
$step = $this->getStep($i);
if ($step->isRequired($this->getValues())) {
$found = true;
} else {
$i--;
}
}
break;
}
return $i;
}
/**
* Gets values stored in session for a given step, or all
* @param string $classname the step to get values for, or all
* @return Array
*/
private function getValues($classname='')
{
if ($classname) {
return $_SESSION['coursewizard'][$this->temp_id][$classname] ?? [];
return $_SESSION['coursewizard'][$this->temp_id] ?? [];

Jan-Hendrik Willms
committed
* @param string $stepclass name of the current step.
* @param mixed $values

Jan-Hendrik Willms
committed
private function setStepValues($stepclass, $values)
{
$_SESSION['coursewizard'][$this->temp_id][$stepclass] = $values;
}
}