Proyectos de Subversion Moodle

Rev

| 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 mod_data;
18
 
19
use context_module;
20
use mod_data\local\exporter\csv_entries_exporter;
21
use mod_data\local\exporter\ods_entries_exporter;
22
use mod_data\local\exporter\utils;
23
 
24
/**
25
 * Unit tests for exporting entries.
26
 *
27
 * @package    mod_data
28
 * @copyright  2023 ISB Bayern
29
 * @author     Philipp Memmel
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
class entries_export_test extends \advanced_testcase {
33
 
34
    /**
35
     * Get the test data.
36
     *
37
     * In this instance we are setting up database records to be used in the unit tests.
38
     *
39
     * @return array of test instances
40
     */
41
    protected function get_test_data(): array {
42
        $this->resetAfterTest(true);
43
 
44
        /** @var \mod_data_generator $generator */
45
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
46
        $course = $this->getDataGenerator()->create_course();
47
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
48
        $this->setUser($teacher);
49
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student', ['username' => 'student']);
50
 
51
        $data = $generator->create_instance(['course' => $course->id]);
52
        $cm = get_coursemodule_from_instance('data', $data->id);
53
 
54
        // Add fields.
55
        $fieldrecord = new \stdClass();
56
        $fieldrecord->name = 'numberfield'; // Identifier of the records for testing.
57
        $fieldrecord->type = 'number';
58
        $numberfield = $generator->create_field($fieldrecord, $data);
59
 
60
        $fieldrecord->name = 'textfield';
61
        $fieldrecord->type = 'text';
62
        $textfield = $generator->create_field($fieldrecord, $data);
63
 
64
        $fieldrecord->name = 'filefield1';
65
        $fieldrecord->type = 'file';
66
        $filefield1 = $generator->create_field($fieldrecord, $data);
67
 
68
        $fieldrecord->name = 'filefield2';
69
        $fieldrecord->type = 'file';
70
        $filefield2 = $generator->create_field($fieldrecord, $data);
71
 
72
        $fieldrecord->name = 'picturefield';
73
        $fieldrecord->type = 'picture';
74
        $picturefield = $generator->create_field($fieldrecord, $data);
75
 
76
        $contents[$numberfield->field->id] = '3';
77
        $contents[$textfield->field->id] = 'a simple text';
78
        $contents[$filefield1->field->id] = 'samplefile.png';
79
        $contents[$filefield2->field->id] = 'samplefile.png';
80
        $contents[$picturefield->field->id] = ['picturefile.png', 'this picture shows something'];
81
        $generator->create_entry($data, $contents);
82
 
83
        return [
84
            'teacher' => $teacher,
85
            'student' => $student,
86
            'data' => $data,
87
            'cm' => $cm,
88
        ];
89
    }
90
 
91
    /**
92
     * Tests the exporting of the content of a mod_data instance by using the csv_entries_exporter.
93
     *
94
     * It also includes more general testing of the functionality of the entries_exporter the csv_entries_exporter
95
     * is inheriting from.
96
     *
97
     * @covers \mod_data\local\exporter\entries_exporter
98
     * @covers \mod_data\local\exporter\entries_exporter::get_records_count()
99
     * @covers \mod_data\local\exporter\entries_exporter::send_file()
100
     * @covers \mod_data\local\exporter\csv_entries_exporter
101
     * @covers \mod_data\local\exporter\utils::data_exportdata
102
     */
103
    public function test_export_csv(): void {
104
        global $DB;
105
        [
106
            'data' => $data,
107
            'cm' => $cm,
108
        ] = $this->get_test_data();
109
 
110
        $exporter = new csv_entries_exporter();
111
        $exporter->set_export_file_name('testexportfile');
112
        $fieldrecords = $DB->get_records('data_fields', ['dataid' => $data->id], 'id');
113
 
114
        $fields = [];
115
        foreach ($fieldrecords as $fieldrecord) {
116
            $fields[] = data_get_field($fieldrecord, $data);
117
        }
118
 
119
        // We select all fields.
120
        $selectedfields = array_map(fn($field) => $field->field->id, $fields);
121
        $currentgroup = groups_get_activity_group($cm);
122
        $context = context_module::instance($cm->id);
123
        $exportuser = false;
124
        $exporttime = false;
125
        $exportapproval = false;
126
        $tags = false;
127
        // We first test the export without exporting files.
128
        // This means file and picture fields will be exported, but only as text (which is the filename),
129
        // so we will receive a csv export file.
130
        $includefiles = false;
131
        utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
132
            $exportuser, $exporttime, $exportapproval, $tags, $includefiles);
133
        $this->assertEquals(file_get_contents(__DIR__ . '/fixtures/test_data_export_without_files.csv'),
134
            $exporter->send_file(false));
135
 
136
        $this->assertEquals(1, $exporter->get_records_count());
137
 
138
        // We now test the export including files. This will generate a zip archive.
139
        $includefiles = true;
140
        $exporter = new csv_entries_exporter();
141
        $exporter->set_export_file_name('testexportfile');
142
        utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
143
            $exportuser, $exporttime, $exportapproval, $tags, $includefiles);
144
        // We now write the zip archive temporary to disc to be able to parse it and assert it has the correct structure.
145
        $tmpdir = make_request_directory();
146
        file_put_contents($tmpdir . '/testexportarchive.zip', $exporter->send_file(false));
147
        $ziparchive = new \zip_archive();
148
        $ziparchive->open($tmpdir . '/testexportarchive.zip');
149
        $expectedfilecontents = [
150
            // The test generator for mod_data uses a copy of field/picture/pix/sample.png as sample file content for the
151
            // file stored in a file and picture field.
152
            // So we expect that this file has to have the same content as sample.png.
153
            // Also, the default value for the subdirectory in the zip archive containing the files is 'files/'.
154
            'files/samplefile.png' => 'mod/data/field/picture/pix/sample.png',
155
            'files/samplefile_1.png' => 'mod/data/field/picture/pix/sample.png',
156
            'files/picturefile.png' => 'mod/data/field/picture/pix/sample.png',
157
            // By checking that the content of the exported csv is identical to the fixture file it is verified
158
            // that the filenames in the csv file correspond to the names of the exported file.
159
            // It also verifies that files with identical file names in different fields (or records) will be numbered
160
            // automatically (samplefile.png, samplefile_1.png, ...).
161
            'testexportfile.csv' => __DIR__ . '/fixtures/test_data_export_with_files.csv'
162
        ];
163
        for ($i = 0; $i < $ziparchive->count(); $i++) {
164
            // We here iterate over all files in the zip archive and check if their content is identical to the files
165
            // in the $expectedfilecontents array.
166
            $filestream = $ziparchive->get_stream($i);
167
            $fileinfo = $ziparchive->get_info($i);
168
            $filecontent = fread($filestream, $fileinfo->size);
169
            $this->assertEquals(file_get_contents($expectedfilecontents[$fileinfo->pathname]), $filecontent);
170
            fclose($filestream);
171
        }
172
        $ziparchive->close();
173
        unlink($tmpdir . '/testexportarchive.zip');
174
    }
175
 
176
    /**
177
     * Tests specific ODS exporting functionality.
178
     *
179
     * @covers \mod_data\local\exporter\ods_entries_exporter
180
     * @covers \mod_data\local\exporter\utils::data_exportdata
181
     */
182
    public function test_export_ods(): void {
183
        global $DB;
184
        [
185
            'data' => $data,
186
            'cm' => $cm,
187
        ] = $this->get_test_data();
188
 
189
        $exporter = new ods_entries_exporter();
190
        $exporter->set_export_file_name('testexportfile');
191
        $fieldrecords = $DB->get_records('data_fields', ['dataid' => $data->id], 'id');
192
 
193
        $fields = [];
194
        foreach ($fieldrecords as $fieldrecord) {
195
            $fields[] = data_get_field($fieldrecord, $data);
196
        }
197
 
198
        // We select all fields.
199
        $selectedfields = array_map(fn($field) => $field->field->id, $fields);
200
        $currentgroup = groups_get_activity_group($cm);
201
        $context = context_module::instance($cm->id);
202
        $exportuser = false;
203
        $exporttime = false;
204
        $exportapproval = false;
205
        $tags = false;
206
        // We first test the export without exporting files.
207
        // This means file and picture fields will be exported, but only as text (which is the filename),
208
        // so we will receive an ods export file.
209
        $includefiles = false;
210
        utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
211
            $exportuser, $exporttime, $exportapproval, $tags, $includefiles);
212
        $odsrows = $this->get_ods_rows_content($exporter->send_file(false));
213
 
214
        // Check, if the headings match with the first row of the ods file.
215
        $i = 0;
216
        foreach ($fields as $field) {
217
            $this->assertEquals($field->field->name, $odsrows[0][$i]);
218
            $i++;
219
        }
220
 
221
        // Check, if the values match with the field values.
222
        $this->assertEquals('3', $odsrows[1][0]);
223
        $this->assertEquals('a simple text', $odsrows[1][1]);
224
        $this->assertEquals('samplefile.png', $odsrows[1][2]);
225
        $this->assertEquals('samplefile.png', $odsrows[1][3]);
226
        $this->assertEquals('picturefile.png', $odsrows[1][4]);
227
 
228
        // As the logic of renaming the files and building a zip archive is implemented in entries_exporter class, we do
229
        // not need to test this for the ods_entries_exporter, because entries_export_test::test_export_csv already does this.
230
    }
231
 
232
    /**
233
     * Helper function to extract the text data as row arrays from an ODS document.
234
     *
235
     * @param string $content the file content
236
     * @return array two-dimensional row/column array with the text content of the first spreadsheet
237
     */
238
    private function get_ods_rows_content(string $content): array {
239
        $file = tempnam(make_request_directory(), 'ods_');
240
        $filestream = fopen($file, "w");
241
        fwrite($filestream, $content);
242
        $reader = new \OpenSpout\Reader\ODS\Reader();
243
        $reader->open($file);
244
        /** @var \OpenSpout\Reader\ODS\Sheet[] $sheets */
245
        $sheets = $reader->getSheetIterator();
246
        $rowscellsvalues = [];
247
        foreach ($sheets as $sheet) {
248
            /** @var \OpenSpout\Common\Entity\Row[] $rows */
249
            $rows = $sheet->getRowIterator();
250
            foreach ($rows as $row) {
251
                $cellvalues = [];
252
                foreach ($row->getCells() as $cell) {
253
                    $cellvalues[] = $cell->getValue();
254
                }
255
                $rowscellsvalues[] = $cellvalues;
256
            }
257
        }
258
        return $rowscellsvalues;
259
    }
260
}