aboutsummaryrefslogtreecommitdiff
path: root/app/controllers/course/wizard.php
blob: f2644441e788ced329c8f006b826b6c2ac7679ee (plain)
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
<?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 array $steps = [];

    public function before_filter (&$action, &$args)
    {
        parent::before_filter($action, $args);

        $GLOBALS['perm']->check('dozent');

    }

    /**
     * Just some sort of placeholder for initial calling without a step number.
     */
    public function index_action()
    {
        $steps = CourseWizardStepRegistry::findBySQL("`enabled` = 1 ORDER BY `number`");

        $parts = [];

        foreach ($steps as $step) {
            $instance = new $step->classname();
            $template = $instance->getStepTemplate([], 0, '');
            if ($template instanceof \Studip\Forms\Form) {
                $template->noButtons()->useStore()->setId($step->classname);
                $parts[] = \Studip\WizardPart::create($step->classname, $template, $step->name);
            }
        }

        $this->render_wizard($parts);
    }

    /**
     * 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,
                    'stgteil_id' => Request::option('stgteil_id')
                ])
            );
        }
        $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)
    {
        $stop = false;
        $next_step = 0;
        $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'])) {
            $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::bool('copy_basic_data');
            $_SESSION['coursewizard'][$this->temp_id]['copy_participants'] = Request::bool('copy_participants');
            $_SESSION['coursewizard'][$this->temp_id]['copy_groups'] = Request::bool('copy_groups');
            $_SESSION['coursewizard'][$this->temp_id]['copy_members'] = Request::bool('copy_members');
            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'])) {
                            // 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())) {

                            if ($this->course->isToolActive(CoreStudygroupAdmin::class)) {
                                $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]);
                            } else {
                                $message = MessageBox::success(sprintf(
                                    _('Die Studien-/Arbeitsgruppe „%s“ wurde angelegt.'),
                                    htmlReady($this->course->name)
                                ));

                                $target = $this->url_for('course/go', ['to' => $this->course->id]);
                            }

                            // "Normal" course.
                        } 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');
                        } else {
                            $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;
        $this->source_course = null;
        if (!$this->getValues()) {
            PageLayout::postError(_('Ihre Session ist abgelaufen, bitte erneut anfangen.'));
            $this->redirect('course/wizard');
        }
        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] ?? [];
        } else {
            return $_SESSION['coursewizard'][$this->temp_id] ?? [];
        }
    }

    /**
     * @param string $stepclass name of the current step.
     * @param mixed $values
     */
    private function setStepValues($stepclass, $values)
    {
        $_SESSION['coursewizard'][$this->temp_id][$stepclass] = $values;
    }

}