Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace tool_usertours;
18
 
19
use tool_usertours\local\forms;
20
use tool_usertours\local\table;
21
use core\notification;
22
 
23
/**
24
 * Tour manager.
25
 *
26
 * @package    tool_usertours
27
 * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
class manager {
31
    /**
32
     * @var ACTION_LISTTOURS      The action to get the list of tours.
33
     */
34
    const ACTION_LISTTOURS = 'listtours';
35
 
36
    /**
37
     * @var ACTION_NEWTOUR        The action to create a new tour.
38
     */
39
    const ACTION_NEWTOUR = 'newtour';
40
 
41
    /**
42
     * @var ACTION_EDITTOUR       The action to edit the tour.
43
     */
44
    const ACTION_EDITTOUR = 'edittour';
45
 
46
    /**
47
     * @var ACTION_MOVETOUR The action to move a tour up or down.
48
     */
49
    const ACTION_MOVETOUR = 'movetour';
50
 
51
    /**
52
     * @var ACTION_EXPORTTOUR     The action to export the tour.
53
     */
54
    const ACTION_EXPORTTOUR = 'exporttour';
55
 
56
    /**
57
     * @var ACTION_IMPORTTOUR     The action to import the tour.
58
     */
59
    const ACTION_IMPORTTOUR = 'importtour';
60
 
61
    /**
62
     * @var ACTION_DELETETOUR     The action to delete the tour.
63
     */
64
    const ACTION_DELETETOUR = 'deletetour';
65
 
66
    /**
67
     * @var ACTION_VIEWTOUR       The action to view the tour.
68
     */
69
    const ACTION_VIEWTOUR = 'viewtour';
70
 
71
    /**
72
     * @var ACTION_DUPLICATETOUR     The action to duplicate the tour.
73
     */
74
    const ACTION_DUPLICATETOUR = 'duplicatetour';
75
 
76
    /**
77
     * @var ACTION_NEWSTEP The action to create a new step.
78
     */
79
    const ACTION_NEWSTEP = 'newstep';
80
 
81
    /**
82
     * @var ACTION_EDITSTEP The action to edit step configuration.
83
     */
84
    const ACTION_EDITSTEP = 'editstep';
85
 
86
    /**
87
     * @var ACTION_MOVESTEP The action to move a step up or down.
88
     */
89
    const ACTION_MOVESTEP = 'movestep';
90
 
91
    /**
92
     * @var ACTION_DELETESTEP The action to delete a step.
93
     */
94
    const ACTION_DELETESTEP = 'deletestep';
95
 
96
    /**
97
     * @var ACTION_VIEWSTEP The action to view a step.
98
     */
99
    const ACTION_VIEWSTEP = 'viewstep';
100
 
101
    /**
102
     * @var ACTION_HIDETOUR The action to hide a tour.
103
     */
104
    const ACTION_HIDETOUR = 'hidetour';
105
 
106
    /**
107
     * @var ACTION_SHOWTOUR The action to show a tour.
108
     */
109
    const ACTION_SHOWTOUR = 'showtour';
110
 
111
    /**
112
     * @var ACTION_RESETFORALL
113
     */
114
    const ACTION_RESETFORALL = 'resetforall';
115
 
116
    /**
117
     * @var CONFIG_SHIPPED_TOUR
118
     */
119
    const CONFIG_SHIPPED_TOUR = 'shipped_tour';
120
 
121
    /**
122
     * @var CONFIG_SHIPPED_FILENAME
123
     */
124
    const CONFIG_SHIPPED_FILENAME = 'shipped_filename';
125
 
126
    /**
127
     * @var CONFIG_SHIPPED_VERSION
128
     */
129
    const CONFIG_SHIPPED_VERSION = 'shipped_version';
130
 
131
    /**
132
     * Helper method to initialize admin page, setting appropriate extra URL parameters
133
     *
134
     * @param string $action
135
     */
136
    protected function setup_admin_externalpage(string $action): void {
137
        admin_externalpage_setup('tool_usertours/tours', '', array_filter([
138
            'action' => $action,
139
            'id' => optional_param('id', 0, PARAM_INT),
140
            'tourid' => optional_param('tourid', 0, PARAM_INT),
141
            'direction' => optional_param('direction', 0, PARAM_INT),
142
        ]));
143
    }
144
 
145
    /**
146
     * This is the entry point for this controller class.
147
     *
148
     * @param   string  $action     The action to perform.
149
     */
150
    public function execute($action) {
151
        global $PAGE;
152
        $this->setup_admin_externalpage($action);
153
        $PAGE->set_primary_active_tab('siteadminnode');
154
 
155
        // Add the main content.
156
        switch ($action) {
157
            case self::ACTION_NEWTOUR:
158
            case self::ACTION_EDITTOUR:
159
                $this->edit_tour(optional_param('id', null, PARAM_INT));
160
                break;
161
 
162
            case self::ACTION_MOVETOUR:
163
                $this->move_tour(required_param('id', PARAM_INT));
164
                break;
165
 
166
            case self::ACTION_EXPORTTOUR:
167
                $this->export_tour(required_param('id', PARAM_INT));
168
                break;
169
 
170
            case self::ACTION_IMPORTTOUR:
171
                $this->import_tour();
172
                break;
173
 
174
            case self::ACTION_VIEWTOUR:
175
                $this->view_tour(required_param('id', PARAM_INT));
176
                break;
177
 
178
            case self::ACTION_DUPLICATETOUR:
179
                $this->duplicate_tour(required_param('id', PARAM_INT));
180
                break;
181
 
182
            case self::ACTION_HIDETOUR:
183
                $this->hide_tour(required_param('id', PARAM_INT));
184
                break;
185
 
186
            case self::ACTION_SHOWTOUR:
187
                $this->show_tour(required_param('id', PARAM_INT));
188
                break;
189
 
190
            case self::ACTION_DELETETOUR:
191
                $this->delete_tour(required_param('id', PARAM_INT));
192
                break;
193
 
194
            case self::ACTION_RESETFORALL:
195
                $this->reset_tour_for_all(required_param('id', PARAM_INT));
196
                break;
197
 
198
            case self::ACTION_NEWSTEP:
199
            case self::ACTION_EDITSTEP:
200
                $this->edit_step(optional_param('id', null, PARAM_INT));
201
                break;
202
 
203
            case self::ACTION_MOVESTEP:
204
                $this->move_step(required_param('id', PARAM_INT));
205
                break;
206
 
207
            case self::ACTION_DELETESTEP:
208
                $this->delete_step(required_param('id', PARAM_INT));
209
                break;
210
 
211
            case self::ACTION_LISTTOURS:
212
            default:
213
                $this->print_tour_list();
214
                break;
215
        }
216
    }
217
 
218
    /**
219
     * Print out the page header.
220
     *
221
     * @param   string  $title     The title to display.
222
     */
223
    protected function header($title = null) {
224
        global $OUTPUT;
225
 
226
        // Print the page heading.
227
        echo $OUTPUT->header();
228
 
229
        if ($title === null) {
230
            $title = get_string('tours', 'tool_usertours');
231
        }
232
 
233
        echo $OUTPUT->heading($title);
234
    }
235
 
236
    /**
237
     * Print out the page footer.
238
     *
239
     * @return void
240
     */
241
    protected function footer() {
242
        global $OUTPUT;
243
 
244
        echo $OUTPUT->footer();
245
    }
246
 
247
    /**
248
     * Print the the list of tours.
249
     */
250
    protected function print_tour_list() {
251
        global $PAGE, $OUTPUT;
252
 
253
        $this->header();
254
        echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours'));
255
        $table = new table\tour_list();
256
        $tours = helper::get_tours();
257
        foreach ($tours as $tour) {
258
            $table->add_data_keyed($table->format_row($tour));
259
        }
260
 
261
        $table->finish_output();
262
        $actions = [
263
            (object) [
264
                'link'  => helper::get_edit_tour_link(),
265
                'linkproperties' => [],
1441 ariadna 266
                'img'   => 'i/tour-new',
1 efrain 267
                'title' => get_string('newtour', 'tool_usertours'),
268
            ],
269
            (object) [
270
                'link'  => helper::get_import_tour_link(),
271
                'linkproperties' => [],
1441 ariadna 272
                'img'   => 'i/tour-import',
1 efrain 273
                'title' => get_string('importtour', 'tool_usertours'),
274
            ],
275
            (object) [
276
                'link'  => new \moodle_url('https://moodle.net/search', ['q' => 'user tours']),
277
                'linkproperties' => [
278
                        'target' => '_blank',
279
                    ],
1441 ariadna 280
                'img'   => 'i/tour-shared',
1 efrain 281
                'title' => get_string('sharedtourslink', 'tool_usertours'),
282
            ],
283
        ];
284
 
285
        echo \html_writer::start_tag('div', [
1441 ariadna 286
                'class' => 'tour-actions mt-3',
1 efrain 287
            ]);
288
 
289
        echo \html_writer::start_tag('ul');
290
        foreach ($actions as $config) {
291
            $action = \html_writer::start_tag('li');
292
            $linkproperties = $config->linkproperties;
293
            $linkproperties['href'] = $config->link;
1441 ariadna 294
            $linkproperties['class'] = 'text-body';
1 efrain 295
            $action .= \html_writer::start_tag('a', $linkproperties);
1441 ariadna 296
            $action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours', ['class' => 'iconsize-medium']);
1 efrain 297
            $action .= \html_writer::div($config->title);
298
            $action .= \html_writer::end_tag('a');
299
            $action .= \html_writer::end_tag('li');
300
            echo $action;
301
        }
302
        echo \html_writer::end_tag('ul');
303
        echo \html_writer::end_tag('div');
304
 
305
        // JS for Tour management.
306
        $PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup');
307
        $this->footer();
308
    }
309
 
310
    /**
311
     * Return the edit tour link.
312
     *
313
     * @param   int         $id     The ID of the tour
314
     * @return string
315
     */
316
    protected function get_edit_tour_link($id = null) {
317
        $addlink = helper::get_edit_tour_link($id);
318
        return \html_writer::link($addlink, get_string('newtour', 'tool_usertours'));
319
    }
320
 
321
    /**
322
     * Print the edit tour link.
323
     *
324
     * @param   int         $id     The ID of the tour
325
     */
326
    protected function print_edit_tour_link($id = null) {
327
        echo $this->get_edit_tour_link($id);
328
    }
329
 
330
    /**
331
     * Get the import tour link.
332
     *
333
     * @return string
334
     */
335
    protected function get_import_tour_link() {
336
        $importlink = helper::get_import_tour_link();
337
        return \html_writer::link($importlink, get_string('importtour', 'tool_usertours'));
338
    }
339
 
340
    /**
341
     * Print the edit tour page.
342
     *
343
     * @param   int         $id     The ID of the tour
344
     */
345
    protected function edit_tour($id = null) {
346
        global $PAGE;
347
        if ($id) {
348
            $tour = tour::instance($id);
349
            $PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_edit_link());
350
        } else {
351
            $tour = new tour();
352
            $PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link());
353
        }
354
 
355
        $form = new forms\edittour($tour);
356
 
357
        if ($form->is_cancelled()) {
358
            redirect(helper::get_list_tour_link());
359
        } else if ($data = $form->get_data()) {
360
            // Creating a new tour.
361
            $tour->set_name($data->name);
362
            $tour->set_description($data->description);
363
            $tour->set_pathmatch($data->pathmatch);
364
            $tour->set_enabled(!empty($data->enabled));
365
            $tour->set_endtourlabel($data->endtourlabel);
366
            $tour->set_display_step_numbers(!empty($data->displaystepnumbers));
367
            $tour->set_showtourwhen($data->showtourwhen);
368
 
369
            foreach (configuration::get_defaultable_keys() as $key) {
370
                $tour->set_config($key, $data->$key);
371
            }
372
 
373
            // Save filter values.
374
            foreach (helper::get_all_filters() as $filterclass) {
375
                $filterclass::save_filter_values_from_form($tour, $data);
376
            }
377
 
378
            $tour->persist();
379
 
380
            redirect(helper::get_list_tour_link());
381
        } else {
382
            if (empty($tour)) {
383
                $this->header('newtour');
384
            } else {
385
                if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
386
                    notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
387
                }
388
 
389
                $tourname = !empty($tour->get_name()) ? helper::get_string_from_input($tour->get_name()) : '';
390
                $this->header($tourname);
391
                $data = $tour->prepare_data_for_form();
392
 
393
                // Prepare filter values for the form.
394
                foreach (helper::get_all_filters() as $filterclass) {
395
                    $filterclass::prepare_filter_values_for_form($tour, $data);
396
                }
397
 
398
                $form->set_data($data);
399
            }
400
 
401
            $form->display();
1441 ariadna 402
            $PAGE->requires->js_call_amd('tool_usertours/tour_filters', 'init');
1 efrain 403
            $this->footer();
404
        }
405
    }
406
 
407
    /**
408
     * Print the export tour page.
409
     *
410
     * @param   int         $id     The ID of the tour
411
     */
412
    protected function export_tour($id) {
413
        $tour = tour::instance($id);
414
 
415
        // Grab the full data record.
416
        $export = $tour->to_record();
417
 
418
        // Remove the id.
419
        unset($export->id);
420
 
421
        // Set the version.
422
        $export->version = get_config('tool_usertours', 'version');
423
 
424
        // Step export.
425
        $export->steps = [];
426
        foreach ($tour->get_steps() as $step) {
427
            $record = $step->to_record(true);
428
            unset($record->id);
429
            unset($record->tourid);
430
 
431
            $export->steps[] = $record;
432
        }
433
 
434
        $exportstring = json_encode($export);
435
 
436
        $filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
437
 
438
        // Force download.
439
        send_file($exportstring, $filename, 0, 0, true, true);
440
    }
441
 
442
    /**
443
     * Handle tour import.
444
     */
445
    protected function import_tour() {
446
        global $PAGE;
447
        $PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link());
448
 
449
        $form = new forms\importtour();
450
 
451
        if ($form->is_cancelled()) {
452
            redirect(helper::get_list_tour_link());
453
        } else if ($form->get_data()) {
454
            // Importing a tour.
455
            $tourconfigraw = $form->get_file_content('tourconfig');
456
            $tour = self::import_tour_from_json($tourconfigraw);
457
 
458
            redirect($tour->get_view_link());
459
        } else {
460
            $this->header();
461
            $form->display();
462
            $this->footer();
463
        }
464
    }
465
 
466
    /**
467
     * Print the view tour page.
468
     *
469
     * @param   int         $tourid     The ID of the tour to display.
470
     */
471
    protected function view_tour($tourid) {
472
        global $PAGE;
473
        $tour = helper::get_tour($tourid);
474
        $tourname = helper::get_string_from_input($tour->get_name());
475
 
476
        $PAGE->navbar->add($tourname, $tour->get_view_link());
477
 
478
        $this->header($tourname);
479
        echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
480
                'tourname'  => $tourname,
481
                'path'      => $tour->get_pathmatch(),
482
            ]));
483
        echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [
484
                'editlink'  => $tour->get_edit_link()->out(),
485
                'resetlink' => $tour->get_reset_link()->out(),
486
            ]));
487
 
488
        $table = new table\step_list($tourid);
489
        foreach ($tour->get_steps() as $step) {
490
            $table->add_data_keyed($table->format_row($step));
491
        }
492
 
493
        $table->finish_output();
494
        $this->print_edit_step_link($tourid);
495
 
496
        // JS for Step management.
497
        $PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup');
498
 
499
        $this->footer();
500
    }
501
 
502
    /**
503
     * Duplicate an existing tour.
504
     *
505
     * @param   int         $tourid     The ID of the tour to duplicate.
506
     */
507
    protected function duplicate_tour($tourid) {
1441 ariadna 508
        require_sesskey();
509
 
1 efrain 510
        $tour = helper::get_tour($tourid);
511
        $export = $tour->to_record();
512
        // Remove the id.
513
        unset($export->id);
514
 
515
        // Set the version.
516
        $export->version = get_config('tool_usertours', 'version');
517
 
518
        $export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name);
519
 
520
        // Step export.
521
        $export->steps = [];
522
        foreach ($tour->get_steps() as $step) {
523
            $record = $step->to_record(true);
524
            unset($record->id);
525
            unset($record->tourid);
526
 
527
            $export->steps[] = $record;
528
        }
529
 
530
        $exportstring = json_encode($export);
531
        $newtour = self::import_tour_from_json($exportstring);
532
 
533
        redirect($newtour->get_view_link());
534
    }
535
 
536
    /**
537
     * Show the tour.
538
     *
539
     * @param   int         $tourid     The ID of the tour to display.
540
     */
541
    protected function show_tour($tourid) {
542
        $this->show_hide_tour($tourid, 1);
543
    }
544
 
545
    /**
546
     * Hide the tour.
547
     *
548
     * @param   int         $tourid     The ID of the tour to display.
549
     */
550
    protected function hide_tour($tourid) {
551
        $this->show_hide_tour($tourid, 0);
552
    }
553
 
554
    /**
555
     * Show or Hide the tour.
556
     *
557
     * @param   int         $tourid     The ID of the tour to display.
558
     * @param   int         $visibility The intended visibility.
559
     */
560
    protected function show_hide_tour($tourid, $visibility) {
561
        global $DB;
562
 
563
        require_sesskey();
564
 
565
        $tour = $DB->get_record('tool_usertours_tours', ['id' => $tourid]);
566
        $tour->enabled = $visibility;
567
        $DB->update_record('tool_usertours_tours', $tour);
568
 
569
        redirect(helper::get_list_tour_link());
570
    }
571
 
572
    /**
573
     * Delete the tour.
574
     *
575
     * @param   int         $tourid     The ID of the tour to remove.
576
     */
577
    protected function delete_tour($tourid) {
578
        require_sesskey();
579
 
580
        $tour = tour::instance($tourid);
581
        $tour->remove();
582
 
583
        redirect(helper::get_list_tour_link());
584
    }
585
 
586
    /**
587
     * Reset the tour state for all users.
588
     *
589
     * @param   int         $tourid     The ID of the tour to remove.
590
     */
591
    protected function reset_tour_for_all($tourid) {
592
        require_sesskey();
593
 
594
        $tour = tour::instance($tourid);
595
        $tour->mark_major_change();
596
 
597
        redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours'));
598
    }
599
 
600
    /**
601
     * Get all tours for the current page URL.
602
     *
603
     * @param   bool        $reset      Forcibly update the current tours
604
     * @return  array
605
     */
606
    public static function get_current_tours($reset = false): array {
607
        global $PAGE;
608
 
609
        static $tours = false;
610
 
611
        if ($tours === false || $reset) {
612
            $tours = self::get_matching_tours($PAGE->url);
613
        }
614
 
615
        return $tours;
616
    }
617
 
618
    /**
619
     * Get all tours matching the specified URL.
620
     *
621
     * @param   moodle_url  $pageurl        The URL to match.
622
     * @return  array
623
     */
624
    public static function get_matching_tours(\moodle_url $pageurl): array {
625
        global $PAGE;
626
 
627
        if (\core_user::awaiting_action()) {
628
            // User not fully ready to use the site. Don't show any tours, we need the user to get properly set up so
629
            // that all require_login() and other bits work as expected.
630
            return [];
631
        }
632
 
633
        $tours = cache::get_matching_tourdata($pageurl);
634
 
635
        $matches = [];
636
        if ($tours) {
637
            $filters = helper::get_all_filters();
638
            foreach ($tours as $record) {
639
                $tour = tour::load_from_record($record);
640
                if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context, $filters)) {
641
                    $matches[] = $tour;
642
                }
643
            }
644
        }
645
 
646
        return $matches;
647
    }
648
 
649
    /**
650
     * Import the provided tour JSON.
651
     *
652
     * @param   string      $json           The tour configuration.
653
     * @return  tour
654
     */
655
    public static function import_tour_from_json($json) {
656
        $tourconfig = json_decode($json);
657
 
658
        // We do not use this yet - we may do in the future.
659
        unset($tourconfig->version);
660
 
661
        $steps = $tourconfig->steps;
662
        unset($tourconfig->steps);
663
 
664
        $tourconfig->id = null;
665
        $tourconfig->sortorder = null;
666
        $tour = tour::load_from_record($tourconfig, true);
667
        $tour->persist(true);
668
 
669
        // Ensure that steps are orderered by their sortorder.
670
        \core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC);
671
 
672
        foreach ($steps as $stepconfig) {
673
            $stepconfig->id = null;
674
            $stepconfig->tourid = $tour->get_id();
675
            $step = step::load_from_record($stepconfig, true, true);
676
            $step->persist(true);
677
        }
678
 
679
        return $tour;
680
    }
681
 
682
    /**
683
     * Helper to fetch the renderer.
684
     *
685
     * @return  renderer
686
     */
687
    protected function get_renderer() {
688
        global $PAGE;
689
        return $PAGE->get_renderer('tool_usertours');
690
    }
691
 
692
    /**
693
     * Print the edit step link.
694
     *
695
     * @param   int     $tourid     The ID of the tour.
696
     * @param   int     $stepid     The ID of the step.
697
     * @return  string
698
     */
699
    protected function print_edit_step_link($tourid, $stepid = null) {
700
        $addlink = helper::get_edit_step_link($tourid, $stepid);
701
        $attributes = [];
702
        if (empty($stepid)) {
703
            $attributes['class'] = 'createstep';
704
        }
705
        echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes);
706
    }
707
 
708
    /**
709
     * Display the edit step form for the specified step.
710
     *
711
     * @param   int     $id     The step to edit.
712
     */
713
    protected function edit_step($id) {
714
        global $PAGE;
715
 
716
        if (isset($id)) {
717
            $step = step::instance($id);
718
        } else {
719
            $step = new step();
720
            $step->set_tourid(required_param('tourid', PARAM_INT));
721
        }
722
 
723
        $tour = $step->get_tour();
724
 
725
        if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
726
            notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
727
        }
728
 
729
        $PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_view_link());
730
        if (isset($id)) {
731
            $PAGE->navbar->add(helper::get_string_from_input($step->get_title()), $step->get_edit_link());
732
        } else {
733
            $PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link());
734
        }
735
 
736
        $form = new forms\editstep($step->get_edit_link(), $step);
737
        if ($form->is_cancelled()) {
738
            redirect($step->get_tour()->get_view_link());
739
        } else if ($data = $form->get_data()) {
740
            $step->handle_form_submission($form, $data);
741
            $step->get_tour()->reset_step_sortorder();
742
            redirect($step->get_tour()->get_view_link());
743
        } else {
744
            if (empty($id)) {
745
                $this->header(get_string('newstep', 'tool_usertours'));
746
            } else {
747
                $this->header(get_string('editstep', 'tool_usertours', helper::get_string_from_input($step->get_title())));
748
            }
749
            $form->set_data($step->prepare_data_for_form());
750
 
751
            $form->display();
752
            $this->footer();
753
        }
754
    }
755
 
756
    /**
757
     * Move a tour up or down and redirect once complete.
758
     *
759
     * @param   int     $id     The tour to move.
760
     */
761
    protected function move_tour($id) {
762
        require_sesskey();
763
 
764
        $direction = required_param('direction', PARAM_INT);
765
 
766
        $tour = tour::instance($id);
767
        self::_move_tour($tour, $direction);
768
 
769
        redirect(helper::get_list_tour_link());
770
    }
771
 
772
    /**
773
     * Move a tour up or down.
774
     *
775
     * @param   tour    $tour   The tour to move.
776
     *
777
     * @param   int     $direction
778
     */
779
    protected static function _move_tour(tour $tour, $direction) {
780
        // We can't move the first tour higher, nor the last tour any lower.
781
        if (
782
            ($tour->is_first_tour() && $direction == helper::MOVE_UP) ||
783
                ($tour->is_last_tour() && $direction == helper::MOVE_DOWN)
784
        ) {
785
            return;
786
        }
787
 
788
        $currentsortorder   = $tour->get_sortorder();
789
        $targetsortorder    = $currentsortorder + $direction;
790
 
791
        $swapwith = helper::get_tour_from_sortorder($targetsortorder);
792
 
793
        // Set the sort order to something out of the way.
794
        $tour->set_sortorder(-1);
795
        $tour->persist();
796
 
797
        // Swap the two sort orders.
798
        $swapwith->set_sortorder($currentsortorder);
799
        $swapwith->persist();
800
 
801
        $tour->set_sortorder($targetsortorder);
802
        $tour->persist();
803
    }
804
 
805
    /**
806
     * Move a step up or down.
807
     *
808
     * @param   int     $id     The step to move.
809
     */
810
    protected function move_step($id) {
811
        require_sesskey();
812
 
813
        $direction = required_param('direction', PARAM_INT);
814
 
815
        $step = step::instance($id);
816
        $currentsortorder   = $step->get_sortorder();
817
        $targetsortorder    = $currentsortorder + $direction;
818
 
819
        $tour = $step->get_tour();
820
        $swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder);
821
 
822
        // Set the sort order to something out of the way.
823
        $step->set_sortorder(-1);
824
        $step->persist();
825
 
826
        // Swap the two sort orders.
827
        $swapwith->set_sortorder($currentsortorder);
828
        $swapwith->persist();
829
 
830
        $step->set_sortorder($targetsortorder);
831
        $step->persist();
832
 
833
        // Reset the sort order.
834
        $tour->reset_step_sortorder();
835
        redirect($tour->get_view_link());
836
    }
837
 
838
    /**
839
     * Delete the step.
840
     *
841
     * @param   int         $stepid     The ID of the step to remove.
842
     */
843
    protected function delete_step($stepid) {
844
        require_sesskey();
845
 
846
        $step = step::instance($stepid);
847
        $tour = $step->get_tour();
848
 
849
        $step->remove();
850
        redirect($tour->get_view_link());
851
    }
852
 
853
    /**
854
     * Make sure all of the default tours that are shipped with Moodle are created
855
     * and up to date with the latest version.
856
     */
857
    public static function update_shipped_tours() {
858
        global $DB, $CFG;
859
 
860
        // A list of tours that are shipped with Moodle. They are in
861
        // the format filename => version. The version value needs to
862
        // be increased if the tour has been updated.
863
        $shippedtours = [
864
            '40_tour_navigation_dashboard.json' => 4,
865
            '40_tour_navigation_mycourse.json' => 5,
866
            '40_tour_navigation_course_teacher.json' => 3,
867
            '40_tour_navigation_course_student.json' => 3,
868
            '42_tour_gradebook_grader_report.json' => 1,
869
        ];
870
 
871
        // These are tours that we used to ship but don't ship any longer.
872
        // We do not remove them, but we do disable them.
873
        $unshippedtours = [
874
            // Formerly included in Moodle 3.2.0.
875
            'boost_administrator.json' => 1,
876
            'boost_course_view.json' => 1,
877
 
878
            // Formerly included in Moodle 3.6.0.
879
            '36_dashboard.json' => 3,
880
            '36_messaging.json' => 3,
881
 
882
            // Formerly included in Moodle 3.11.0.
883
            '311_activity_information_activity_page_student.json' => 2,
884
            '311_activity_information_activity_page_teacher.json' => 2,
885
            '311_activity_information_course_page_student.json' => 2,
886
            '311_activity_information_course_page_teacher.json' => 2,
887
        ];
888
 
889
        $existingtourrecords = $DB->get_recordset('tool_usertours_tours');
890
 
891
        // Get all of the existing shipped tours and check if they need to be
892
        // updated.
893
        foreach ($existingtourrecords as $tourrecord) {
894
            $tour = tour::load_from_record($tourrecord);
895
 
896
            if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
897
                $filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME);
898
                $version = $tour->get_config(self::CONFIG_SHIPPED_VERSION);
899
 
900
                // If we know about this tour (otherwise leave it as is).
901
                if (isset($shippedtours[$filename])) {
902
                    // And the version in the DB is an older version.
903
                    if ($version < $shippedtours[$filename]) {
904
                        // Remove the old version because it's been updated
905
                        // and needs to be recreated.
906
                        $tour->remove();
907
                    } else {
908
                        // The tour has not been updated so we don't need to
909
                        // do anything with it.
910
                        unset($shippedtours[$filename]);
911
                    }
912
                }
913
 
914
                if (isset($unshippedtours[$filename])) {
915
                    if ($version <= $unshippedtours[$filename]) {
916
                        $tour = tour::instance($tour->get_id());
917
                        $tour->set_enabled(tour::DISABLED);
918
                        $tour->persist();
919
                    }
920
                }
921
            }
922
        }
923
        $existingtourrecords->close();
924
 
925
        // Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours.
926
        helper::reset_tour_sortorder();
927
 
928
        foreach (array_reverse($shippedtours) as $filename => $version) {
929
            $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
930
            $tourjson = file_get_contents($filepath);
931
            $tour = self::import_tour_from_json($tourjson);
932
 
933
            // Set some additional config data to record that this tour was
934
            // added as a shipped tour.
935
            $tour->set_config(self::CONFIG_SHIPPED_TOUR, true);
936
            $tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename);
937
            $tour->set_config(self::CONFIG_SHIPPED_VERSION, $version);
938
 
939
            // Bump new tours to the top of the list.
940
            while ($tour->get_sortorder() > 0) {
941
                self::_move_tour($tour, helper::MOVE_UP);
942
            }
943
 
944
            if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) {
945
                // Disable this tour if this is behat or phpunit.
946
                $tour->set_enabled(false);
947
            }
948
 
949
            $tour->persist();
950
        }
951
    }
952
}