Skip to content

Commit e58bd46

Browse files
committed
Issue #17: Add support for export/import of CMS types.
1 parent 4dc3ba9 commit e58bd46

File tree

103 files changed

+13808
-85
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+13808
-85
lines changed

classes/exportable.php

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 mod_cms;
18+
19+
use Symfony\Component\Yaml\Yaml;
20+
21+
/**
22+
* Exportability
23+
*
24+
* @package mod_cms
25+
* @author Kevin Pham <[email protected]>
26+
* @copyright Catalyst IT, 2022
27+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28+
*/
29+
trait exportable {
30+
/** @var string Mime type for the downloaded file. */
31+
private $mimetype = 'application/x-yaml';
32+
33+
/**
34+
* Exports this data in YAML format.
35+
*
36+
* @param string $format Either 'yaml', 'txt' or 'preview'.
37+
*/
38+
public function export(string $format = 'yaml') {
39+
if ($format == 'preview') {
40+
// Preview in the browser.
41+
header("Content-Type: text/plain\n");
42+
} else if ($format == 'txt') {
43+
$this->send_header($this->get_filename() . '.txt');
44+
} else {
45+
$this->send_header($this->get_filename());
46+
}
47+
echo Yaml::dump(
48+
$this->get_for_export(),
49+
helper::YAML_DUMP_INLINE_LEVEL,
50+
helper::YAML_DUMP_INDENT_LEVEL,
51+
Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | YAML::DUMP_OBJECT_AS_MAP
52+
);
53+
}
54+
55+
/**
56+
* Output file headers to initialise the download of the file.
57+
*
58+
* @param string $filename The name of the file.
59+
*/
60+
private function send_header(string $filename) {
61+
if (defined('BEHAT_SITE_RUNNING') || PHPUNIT_TEST) {
62+
// For text based formats - we cannot test the output with behat if we force a file download.
63+
return;
64+
}
65+
if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
66+
header('Cache-Control: max-age=10');
67+
header('Pragma: ');
68+
} else { // Normal http - prevent caching at all cost.
69+
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
70+
header('Pragma: no-cache');
71+
}
72+
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
73+
header("Content-Type: $this->mimetype\n");
74+
header("Content-Disposition: attachment; filename=\"$filename\"");
75+
}
76+
77+
/**
78+
* Get the filename for the exported file.
79+
*
80+
* @param int|null $timestamp The Unix timestamp to use in the file name.
81+
* @return string $filename
82+
*/
83+
private function get_filename(?int $timestamp = null): string {
84+
$now = $now ?? time();
85+
86+
$filename = str_replace(' ', '_', $this->get('name'));
87+
$filename = clean_filename($filename);
88+
$filename .= clean_filename('_' . gmdate('Ymd_Hi', $now));
89+
$filename .= '.yml';
90+
91+
return $filename;
92+
}
93+
}

classes/form/cms_types_form.php

+4-7
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,8 @@
2525
namespace mod_cms\form;
2626

2727
use core\form\persistent as persistent_form;
28-
use html_writer;
2928
use mod_cms\local\datasource\base as dsbase;
3029
use mod_cms\local\renderer;
31-
use moodle_url;
32-
use stdClass;
3330

3431
/**
3532
* Form for manipulating the content types
@@ -73,12 +70,12 @@ protected function definition() {
7370
// Generate the help text for mustache template.
7471
$cmstype = $this->get_persistent();
7572
$renderer = new renderer($cmstype);
76-
$syntaxlink = html_writer::link(
77-
new moodle_url('https://moodledev.io/docs/guides/templates'),
73+
$syntaxlink = \html_writer::link(
74+
new \moodle_url('https://moodledev.io/docs/guides/templates'),
7875
get_string('mustache_template', 'cms')
7976
);
8077
$helptext = get_string('mustache_help', 'cms', $syntaxlink);
81-
$helptext .= html_writer::table($renderer->get_data_as_table());
78+
$helptext .= \html_writer::table($renderer->get_data_as_table());
8279
$mform->addElement('static', 'mustache_help', '', $helptext);
8380

8481
// Add form elements for data sources.
@@ -123,7 +120,7 @@ public function add_action_buttons($cancel = true, $submitlabel=null) {
123120
*
124121
* Extend this class if you need to add more conversion.
125122
*
126-
* @return stdClass
123+
* @return \stdClass
127124
*/
128125
protected function get_default_data() {
129126
$data = parent::get_default_data();
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
// This file is part of Moodle - https://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 mod_cms\form;
18+
19+
use Symfony\Component\Yaml\Exception\ParseException;
20+
use Symfony\Component\Yaml\Yaml;
21+
22+
/**
23+
* Import form for CMS types.
24+
*
25+
* @package mod_cms
26+
* @author Jason den Dulk <[email protected]>
27+
* @copyright 2023, Catalyst IT
28+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29+
*/
30+
class cms_types_import_form extends \moodleform {
31+
32+
/**
33+
* Build form for importing workflows.
34+
*
35+
* {@inheritDoc}
36+
* @see \moodleform::definition()
37+
*/
38+
public function definition() {
39+
40+
$mform = $this->_form;
41+
42+
// Workflow file.
43+
$mform->addElement(
44+
'filepicker',
45+
'importfile',
46+
get_string('import_file', 'mod_cms'),
47+
null,
48+
['maxbytes' => 256000, 'accepted_types' => ['.yml', '.yaml', '.txt']]
49+
);
50+
$mform->addRule('importfile', get_string('required'), 'required');
51+
52+
$this->add_action_buttons();
53+
}
54+
55+
/**
56+
* Validate uploaded YAML file.
57+
*
58+
* @param array $data array of ("fieldname"=>value) of submitted data
59+
* @param array $files array of uploaded files "element_name"=>tmp_file_path
60+
* @return array of "element_name"=>"error_description" if there are errors,
61+
* or an empty array if everything is OK (true allowed for backwards compatibility too).
62+
*/
63+
public function validation($data, $files) {
64+
global $USER;
65+
66+
$validationerrors = [];
67+
68+
// Get the file from the filestystem. $files will always be empty.
69+
$fs = get_file_storage();
70+
71+
$context = \context_user::instance($USER->id);
72+
$itemid = $data['importfile'];
73+
74+
// This is how core gets files in this case.
75+
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $itemid, 'id DESC', false)) {
76+
$validationerrors['nofile'] = get_string('error:no_file_uploaded', 'mod_cms');
77+
return $validationerrors;
78+
}
79+
$file = reset($files);
80+
81+
// Check if file is valid YAML.
82+
$content = $file->get_content();
83+
if (!empty($content)) {
84+
$validation = self::validate_yaml($content);
85+
if ($validation !== true) {
86+
$validationerrors['importfile'] = $validation;
87+
}
88+
}
89+
90+
return $validationerrors;
91+
}
92+
93+
/**
94+
* Get the errors returned during form validation.
95+
*
96+
* @return array|mixed
97+
*/
98+
public function get_errors() {
99+
$form = $this->_form;
100+
$errors = $form->_errors;
101+
102+
return $errors;
103+
}
104+
105+
/**
106+
* Validate a YAML string to parse into an object.
107+
*
108+
* @param string $yaml
109+
* @return true|\lang_string Either true, or a string documenting the error.
110+
*/
111+
public static function validate_yaml(string $yaml) {
112+
$invalid = false;
113+
try {
114+
$parsed = Yaml::parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP);
115+
if (isset($parsed) && !is_object($parsed)) {
116+
$invalid = true;
117+
}
118+
} catch (ParseException $e) {
119+
$invalid = true;
120+
}
121+
122+
if ($invalid) {
123+
return new \lang_string('invalidyaml', 'mod_cms');
124+
}
125+
126+
return true;
127+
}
128+
}

classes/helper.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
*/
2525
namespace mod_cms;
2626

27-
use pix_icon;
28-
2927
/**
3028
* Helper methods for mod_cms
3129
*
@@ -34,6 +32,12 @@
3432
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3533
*/
3634
class helper {
35+
/** The level where you switch to inline YAML. */
36+
public const YAML_DUMP_INLINE_LEVEL = 5;
37+
38+
/** The amount of spaces to use for indentation of nested nodes. */
39+
public const YAML_DUMP_INDENT_LEVEL = 2;
40+
3741
/**
3842
* Get a filler icon for display in the actions column of a table.
3943
*
@@ -51,7 +55,7 @@ public static function format_icon_link(string $url, string $icon, string $alt,
5155

5256
return $OUTPUT->action_icon(
5357
$url,
54-
new pix_icon($icon, $alt, $iconcomponent, ['title' => $alt]),
58+
new \pix_icon($icon, $alt, $iconcomponent, ['title' => $alt]),
5559
null,
5660
$options
5761
);

classes/importable.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
// This file is part of Moodle - https://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 mod_cms;
18+
19+
use Symfony\Component\Yaml\Yaml;
20+
21+
/**
22+
* Importability.
23+
*
24+
* @package mod_cms
25+
* @author Jason den Dulk <[email protected]>
26+
* @copyright 2023, Catalyst IT
27+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28+
*/
29+
trait importable {
30+
/**
31+
* Import from YAML data.
32+
*
33+
* @param string $filedata
34+
*/
35+
public function import(string $filedata) {
36+
$content = Yaml::parse($filedata, Yaml::PARSE_OBJECT_FOR_MAP);
37+
$this->set_from_import($content);
38+
}
39+
}

classes/local/datasource/base.php

+39
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,28 @@ public static function get_datasources($cms) {
109109
}
110110
}
111111

112+
/**
113+
* Get a datasource.
114+
*
115+
* @param string $name The short name of the datasource
116+
* @param cms|cms_types $cms
117+
* @return false|base The datasource, or false if not found.
118+
*/
119+
public static function get_datasource(string $name, $cms) {
120+
self::register_datasources();
121+
122+
if (!isset(self::$datasourceclasses[$name])) {
123+
return false;
124+
}
125+
126+
if ($cms instanceof cms_types) {
127+
$cms = $cms->get_sample_cms();
128+
}
129+
130+
$dsclassname = self::$datasourceclasses[$name];
131+
return new $dsclassname($cms);
132+
}
133+
112134
/** @var cms */
113135
protected $cms;
114136

@@ -227,4 +249,21 @@ public function config_action_link(): ?string {
227249
*/
228250
public function config_on_update($data) {
229251
}
252+
253+
/**
254+
* Get configuration data for exporting.
255+
*
256+
* @return \stdClass|null
257+
*/
258+
public function get_for_export(): ?\stdClass {
259+
return null;
260+
}
261+
262+
/**
263+
* Import configuration from an object.
264+
*
265+
* @param \stdClass $data
266+
*/
267+
public function set_from_import(\stdClass $data) {
268+
}
230269
}

0 commit comments

Comments
 (0)