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 pix/monologo.png as sample file content for the file stored in a 151 // file and picture field. 152 // So we expect that this file has to have the same content as monologo.png. 153 // Also, the default value for the subdirectory in the zip archive containing the files is 'files/'. 154 'files/samplefile.png' => 'mod/data/pix/monologo.png', 155 'files/samplefile_1.png' => 'mod/data/pix/monologo.png', 156 'files/picturefile.png' => 'mod/data/pix/monologo.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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body