first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_files;
use advanced_testcase;
use core_files\local\archive_writer\zip_writer;
/**
* Unit tests for \core_files\archive_writer.
*
* @package core_files
* @category test
* @copyright 2020 Mark Nelson <mdjnelson@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_files\archive_writer
*/
class archive_writer_test extends advanced_testcase {
/**
* Test get_file_writer().
*/
public function test_get_file_writer(): void {
$zipwriter = archive_writer::get_file_writer('file.zip', archive_writer::ZIP_WRITER);
$this->assertInstanceOf(zip_writer::class, $zipwriter);
$this->assertTrue(file_exists($zipwriter->get_path_to_zip()));
}
/**
* Test get_stream_writer().
*/
public function test_get_stream_writer(): void {
$zipwriter = archive_writer::get_stream_writer('path/to/file.txt', archive_writer::ZIP_WRITER);
$this->assertInstanceOf(zip_writer::class, $zipwriter);
}
}
@@ -0,0 +1,39 @@
@core @core_files @_file_upload
Feature: Add a new custom file type
In order to add files of a custom type
As an admin
I need to add a new custom file type
@javascript
Scenario: Add custom file type
Given the following "courses" exist:
| fullname | shortname | category | legacyfiles |
| Course 1 | C1 | 0 | 2 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "admin"
And I navigate to "Server > File types" in site administration
And I press "Add a new file type"
And I set the following fields to these values:
| Extension | mdlr |
| MIME type | application/x-moodle-rules |
| File icon | document |
| Description type | Custom description specified in this form |
| Custom description | Moodle rules |
And I press "Save changes"
And I should see "application/x-moodle-rules"
And I log in as "teacher1"
When I add a resource activity to course "Course 1" section "1" and I fill the form with:
| Name | Test file |
| Select files | files/tests/fixtures/custom_filetype.mdlr |
| Show size | 1 |
| Show type | 1 |
| Display resource description | 1 |
And I am on "Course 1" course homepage
Then I should see "Test file"
And I should see "MDLR" in the "span.activitybadge" "css_element"
And I should not see "MDLR" in the "span.resourcelinkdetails" "css_element"
+35
View File
@@ -0,0 +1,35 @@
@core @core_files
Feature: Course files
In order to add legacy files
As a user
I need to upload files
@javascript
Scenario: Add legacy files
Given the following "courses" exist:
| fullname | shortname | category | legacyfiles |
| Course 1 | C1 | 0 | 2 |
And the following config values are set as admin:
| legacyfilesinnewcourses | 1 |
| legacyfilesaddallowed | 1 |
When I log in as "admin"
And I am on "Course 1" course homepage
Then I navigate to "Legacy course files" in current page administration
And I press "Edit legacy course files"
And "Add..." "link" should be visible
And "Create folder" "link" should be visible
@javascript
Scenario: Add legacy file disabled
Given the following "courses" exist:
| fullname | shortname | category | legacyfiles |
| Course 1 | C1 | 0 | 2 |
And the following config values are set as admin:
| legacyfilesinnewcourses | 1 |
| legacyfilesaddallowed | 0 |
When I log in as "admin"
And I am on "Course 1" course homepage
Then I navigate to "Legacy course files" in current page administration
And I press "Edit legacy course files"
And "Add..." "link" should not be visible
And "Create folder" "link" should not be visible
@@ -0,0 +1,56 @@
@core @core_files
Feature: View licence links
In order to get select the applicable licence when uploading a file
As a user
I need to be able to navigate to a page containing licence terms from the file manager
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
@javascript
Scenario: Uploading a file displays licence list modal
Given I log in as "admin"
And I follow "Manage private files..."
And I follow "Add..."
And I follow "Upload a file"
And I click on "Help with Choose licence" "icon" in the "File picker" "dialogue"
Then I should see "Follow these links for further information on the available licence options:"
@javascript @_file_upload
Scenario: Altering a file should display licence list modal
Given I log in as "admin"
And I follow "Manage private files..."
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
And I press "Save changes"
And I follow "Manage private files..."
And I click on "empty.txt" "link" in the "Manage private files" "dialogue"
And I click on "Help with Choose licence" "icon"
Then I should see "Follow these links for further information on the available licence options:"
@javascript @_file_upload
Scenario: Recent files should display licence list modal
Given I log in as "admin"
And I follow "Manage private files..."
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
And I press "Save changes"
And I follow "Manage private files..."
And I follow "Add..."
And I click on "Recent files" "link" in the "File picker" "dialogue"
And I click on "empty.txt" "link" in the "File picker" "dialogue"
And I click on "Help with Choose licence" "icon" in the ".fp-setlicense" "css_element"
Then I should see "Follow these links for further information on the available licence options:"
@javascript @_file_upload
Scenario: Private files should display licence list modal
Given I log in as "admin"
And I follow "Manage private files..."
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
And I press "Save changes"
And I follow "Manage private files..."
And I follow "Add..."
And I click on "Private files" "link" in the "File picker" "dialogue"
And I click on "empty.txt" "link" in the "File picker" "dialogue"
And I click on "Help with Choose licence" "icon" in the ".fp-setlicense" "css_element"
Then I should see "Follow these links for further information on the available licence options:"
+494
View File
@@ -0,0 +1,494 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_files;
/**
* PHPUnit tests for conversion persistent.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class conversion_test extends \advanced_testcase {
/**
* Helper to create a stored file object with the given supplied content.
*
* @param string $filecontent The content of the mocked file
* @param string $filename The file name to use in the stored_file
* @param string $filerecord Any overrides to the filerecord
* @return \stored_file
*/
protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
$filerecord = array_merge([
'contextid' => \context_system::instance()->id,
'component' => 'core',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => $filename,
], $filerecord);
$fs = get_file_storage();
$file = $fs->create_file_from_string($filerecord, $filecontent);
return $file;
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record with matching sourcefileid and targetformat.
*/
public function test_get_conversions_for_file_existing_conversion_incomplete(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$existing = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$existing->create();
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$conversionfile = $conversion->get_sourcefile();
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
$this->assertFalse($conversion->get_destfile());
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record with matching sourcefileid and targetformat when a file with the same
* contenthash is uploaded several times.
*
* @covers \core_files\conversion::get_conversions_for_file
*/
public function test_get_conversions_for_multiple_files_existing_conversion_incomplete(): void {
$this->resetAfterTest();
// Create a bunch of files with the same content.
for ($i = 0; $i < 5; $i++) {
$sourcefiles[] = $this->create_stored_file('test content', 'testfile' . $i . '.txt');
}
// Use only one file for the conversion.
// Pick some file in the middle.
$sourcefile = $sourcefiles[count($sourcefiles) - 2];
$existing = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$existing->create();
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$conversionfile = $conversion->get_sourcefile();
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
$this->assertFalse($conversion->get_destfile());
// Check that getting the conversion for a different file record with the same contenthash
// returns the same conversion as above.
$conversions = conversion::get_conversions_for_file($sourcefiles[count($sourcefiles) - 1], 'pdf');
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$conversionfile = $conversion->get_sourcefile();
$this->assertEquals($existing->get('id'), $conversion->get('id'));
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
$this->assertFalse($conversion->get_destfile());
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record with matching sourcefileid and targetformat when a second
* conversion to a different format exists.
*/
public function test_get_conversions_for_file_existing_conversion_multiple_formats_incomplete(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$existing = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$existing->create();
$second = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'doc',
]);
$second->create();
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$conversionfile = $conversion->get_sourcefile();
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
$this->assertFalse($conversion->get_destfile());
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record with matching sourcefileid and targetformat.
*/
public function test_get_conversions_for_file_existing_conversion_complete(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$destfile = $this->create_stored_file(
'example content',
$sourcefile->get_contenthash(),
[
'component' => 'core',
'filearea' => 'documentconversion',
'filepath' => '/pdf/',
]);
$existing = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
'destfileid' => $destfile->get_id(),
]);
$existing->create();
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
// Only one file should be returned.
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$this->assertEquals($existing->get('id'), $conversion->get('id'));
$this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
$this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record with matching sourcefileid and targetformat.
*/
public function test_get_conversions_for_file_existing_conversion_multiple_formats_complete(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$destfile = $this->create_stored_file(
'example content',
$sourcefile->get_contenthash(),
[
'component' => 'core',
'filearea' => 'documentconversion',
'filepath' => '/pdf/',
]);
$existing = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
'destfileid' => $destfile->get_id(),
]);
$existing->create();
$second = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'doc',
]);
$second->create();
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
// Only one file should be returned.
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
$this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
}
/**
* Ensure that get_conversions_for_file returns an existing conversion
* record does not exist, but the file has previously been converted.
*/
public function test_get_conversions_for_file_existing_target(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$destfile = $this->create_stored_file(
'example content',
$sourcefile->get_contenthash(),
[
'component' => 'core',
'filearea' => 'documentconversion',
'filepath' => '/pdf/',
]);
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
$this->assertCount(1, $conversions);
$conversion = array_shift($conversions);
$conversionsource = $conversion->get_sourcefile();
$this->assertEquals($sourcefile->get_id(), $conversionsource->get_id());
$conversiondest = $conversion->get_destfile();
$this->assertEquals($destfile->get_id(), $conversiondest->get_id());
}
/**
* Ensure that set_sourcefile sets the correct fileid.
*/
public function test_set_sourcefile(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) []);
$conversion->set_sourcefile($sourcefile);
$this->assertEquals($sourcefile->get_id(), $conversion->get('sourcefileid'));
$this->assertNull($conversion->get('destfileid'));
}
/**
* Ensure that store_destfile_from_path stores the file as expected.
*/
public function test_store_destfile_from_path(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$fixture = __FILE__;
$conversion->store_destfile_from_path($fixture);
$destfile = $conversion->get_destfile();
$this->assertEquals(file_get_contents($fixture), $destfile->get_content());
}
/**
* Ensure that store_destfile_from_path stores the file as expected.
*/
public function test_store_destfile_from_path_delete_existing(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$record = [
'contextid' => \context_system::instance()->id,
'component' => 'core',
'filearea' => 'documentconversion',
'itemid' => 0,
'filepath' => '/pdf/',
];
$existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
$fixture = __FILE__;
$conversion->store_destfile_from_path($fixture);
$destfile = $conversion->get_destfile();
$this->assertEquals(file_get_contents($fixture), $destfile->get_content());
}
/**
* Ensure that store_destfile_from_path stores the file as expected.
*/
public function test_store_destfile_from_string(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$fixture = 'Example content';
$conversion->store_destfile_from_string($fixture);
$destfile = $conversion->get_destfile();
$this->assertEquals($fixture, $destfile->get_content());
}
/**
* Ensure that store_destfile_from_string stores the file as expected when
* an existing destfile is found.
*/
public function test_store_destfile_from_string_delete_existing(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$record = [
'contextid' => \context_system::instance()->id,
'component' => 'core',
'filearea' => 'documentconversion',
'itemid' => 0,
'filepath' => '/pdf/',
];
$existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
$fixture = 'Example content';
$conversion->store_destfile_from_string($fixture);
$destfile = $conversion->get_destfile();
$this->assertEquals($fixture, $destfile->get_content());
}
/**
* Ensure that the get_status functions cast the status to integer correctly.
*/
public function test_get_status(): void {
$conversion = new conversion(0, (object) [
'status' => (string) 1,
]);
$this->assertIsInt($conversion->get('status'));
}
/**
* Ensure that get_converter_instance returns false when no converter is set.
*/
public function test_get_converter_instance_none_set(): void {
$conversion = new conversion(0, (object) []);
$this->assertFalse($conversion->get_converter_instance());
}
/**
* Ensure that get_converter_instance returns false when no valid converter is set.
*/
public function test_get_converter_instance_invalid_set(): void {
$conversion = new conversion(0, (object) [
'converter' => '\\fileconverter_not_a_valid_converter\\converter',
]);
$this->assertFalse($conversion->get_converter_instance());
}
/**
* Ensure that get_converter_instance returns an instance when a valid converter is set.
*/
public function test_get_converter_instance_valid_set(): void {
$conversion = new conversion(0, (object) [
'converter' => \fileconverter_unoconv\converter::class,
]);
$this->assertInstanceOf(\fileconverter_unoconv\converter::class, $conversion->get_converter_instance());
}
/**
* Test that all old conversion records are removed periodically.
*/
public function test_remove_old_conversion_records_old(): void {
$this->resetAfterTest();
global $DB;
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$conversion->create();
$DB->set_field(conversion::TABLE, 'timemodified', time() - YEARSECS);
conversion::remove_old_conversion_records();
$this->assertEquals(0, $DB->count_records(conversion::TABLE));
}
/**
* Test that all old conversion records are removed periodically.
*/
public function test_remove_old_conversion_records_young(): void {
$this->resetAfterTest();
global $DB;
$sourcefile = $this->create_stored_file();
$conversion = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$conversion->create();
$DB->set_field(conversion::TABLE, 'timemodified', time() - DAYSECS);
conversion::remove_old_conversion_records();
$this->assertEquals(1, $DB->count_records(conversion::TABLE));
}
/**
* Test orphan records are removed.
*/
public function test_remove_orphan_records(): void {
global $DB;
$this->resetAfterTest();
$sf1 = $this->create_stored_file('1', '1');
$sf2 = $this->create_stored_file('2', '2');
$sf3 = $this->create_stored_file('3', '3');
$c1 = new conversion(0, (object) ['sourcefileid' => $sf1->get_id(), 'targetformat' => 'pdf']);
$c1->create();
$c2 = new conversion(0, (object) ['sourcefileid' => $sf2->get_id(), 'targetformat' => 'pdf']);
$c2->create();
$c3 = new conversion(0, (object) ['sourcefileid' => $sf3->get_id(), 'targetformat' => 'pdf']);
$c3->create();
$this->assertTrue(conversion::record_exists($c1->get('id')));
$this->assertTrue(conversion::record_exists($c2->get('id')));
$this->assertTrue(conversion::record_exists($c3->get('id')));
// Nothing should happen here.
conversion::remove_orphan_records();
$this->assertTrue(conversion::record_exists($c1->get('id')));
$this->assertTrue(conversion::record_exists($c2->get('id')));
$this->assertTrue(conversion::record_exists($c3->get('id')));
// Delete file #2.
$sf2->delete();
conversion::remove_orphan_records();
$this->assertTrue(conversion::record_exists($c1->get('id')));
$this->assertFalse(conversion::record_exists($c2->get('id')));
$this->assertTrue(conversion::record_exists($c3->get('id')));
// Delete file #1, #3.
$sf1->delete();
$sf3->delete();
conversion::remove_orphan_records();
$this->assertFalse(conversion::record_exists($c1->get('id')));
$this->assertFalse(conversion::record_exists($c2->get('id')));
$this->assertFalse(conversion::record_exists($c3->get('id')));
}
}
+917
View File
@@ -0,0 +1,917 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* PHPUnit tests for fileconverter API.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_files\conversion;
use core_files\converter;
/**
* PHPUnit tests for fileconverter API.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class converter_test extends advanced_testcase {
/**
* Get a testable mock of the abstract files_converter class.
*
* @param array $mockedmethods A list of methods you intend to override
* If no methods are specified, only abstract functions are mocked.
* @return \core_files\converter
*/
protected function get_testable_mock($mockedmethods = []) {
$converter = $this->getMockBuilder(\core_files\converter::class)
->onlyMethods($mockedmethods)
->getMockForAbstractClass();
return $converter;
}
/**
* Get a testable mock of the conversion.
*
* @param array $mockedmethods A list of methods you intend to override
* @return \core_files\conversion
*/
protected function get_testable_conversion($mockedmethods = []) {
$conversion = $this->getMockBuilder(\core_files\conversion::class)
->onlyMethods($mockedmethods)
->setConstructorArgs([0, (object) []])
->getMock();
return $conversion;
}
/**
* Get a testable mock of the abstract files_converter class.
*
* @param array $mockedmethods A list of methods you intend to override
* If no methods are specified, only abstract functions are mocked.
* @return \core_files\converter_interface
*/
protected function get_mocked_converter($mockedmethods = []) {
$converter = $this->getMockBuilder(\core_files\converter_interface::class)
->onlyMethods($mockedmethods)
->getMockForAbstractClass();
return $converter;
}
/**
* Helper to create a stored file objectw with the given supplied content.
*
* @param string $filecontent The content of the mocked file
* @param string $filename The file name to use in the stored_file
* @param array $mockedmethods A list of methods you intend to override
* If no methods are specified, only abstract functions are mocked.
* @return stored_file
*/
protected function get_stored_file($filecontent = 'content', $filename = null, $filerecord = [], $mockedmethods = []) {
global $CFG;
$contenthash = sha1($filecontent);
if (empty($filename)) {
$filename = $contenthash;
}
$filerecord['contenthash'] = $contenthash;
$filerecord['filesize'] = strlen($filecontent);
$filerecord['filename'] = $filename;
$filerecord['id'] = 42;
$file = $this->getMockBuilder(stored_file::class)
->onlyMethods($mockedmethods)
->setConstructorArgs([get_file_storage(), (object) $filerecord])
->getMock();
return $file;
}
/**
* Helper to create a stored file object with the given supplied content.
*
* @param string $filecontent The content of the mocked file
* @param string $filename The file name to use in the stored_file
* @param string $filerecord Any overrides to the filerecord
* @return stored_file
*/
protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
$filerecord = array_merge([
'contextid' => context_system::instance()->id,
'component' => 'core',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => $filename,
], $filerecord);
$fs = get_file_storage();
$file = $fs->create_file_from_string($filerecord, $filecontent);
return $file;
}
/**
* Get a mock of the file_storage API.
*
* @param array $mockedmethods A list of methods you intend to override
* @return file_storage
*/
protected function get_file_storage_mock($mockedmethods = []) {
$fs = $this->getMockBuilder(\file_storage::class)
->onlyMethods($mockedmethods)
->disableOriginalConstructor()
->getMock();
return $fs;
}
/**
* Test the start_conversion function.
*/
public function test_start_conversion_existing_single(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$first = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$first->create();
$converter = $this->get_testable_mock(['poll_conversion']);
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
// The old conversions should still be present and match the one returned.
$this->assertEquals($first->get('id'), $conversion->get('id'));
}
/**
* Test the start_conversion function.
*/
public function test_start_conversion_existing_multiple(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$first = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$first->create();
$second = new conversion(0, (object) [
'sourcefileid' => $sourcefile->get_id(),
'targetformat' => 'pdf',
]);
$second->create();
$converter = $this->get_testable_mock(['poll_conversion']);
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
// The old conversions should have been removed.
$this->assertFalse(conversion::get_record(['id' => $first->get('id')]));
$this->assertFalse(conversion::get_record(['id' => $second->get('id')]));
}
/**
* Test the start_conversion function.
*/
public function test_start_conversion_no_existing(): void {
$this->resetAfterTest();
$sourcefile = $this->create_stored_file();
$converter = $this->get_testable_mock(['poll_conversion']);
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
$this->assertInstanceOf(\core_files\conversion::class, $conversion);
}
/**
* Test the get_document_converter_classes function with no enabled plugins.
*/
public function test_get_document_converter_classes_no_plugins(): void {
$converter = $this->get_testable_mock(['get_enabled_plugins']);
$converter->method('get_enabled_plugins')->willReturn([]);
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
$this->assertEmpty($result);
}
/**
* Test the get_document_converter_classes function when no class was found.
*/
public function test_get_document_converter_classes_plugin_class_not_found(): void {
$converter = $this->get_testable_mock(['get_enabled_plugins']);
$converter->method('get_enabled_plugins')->willReturn([
'noplugin' => '\not\a\real\plugin',
]);
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
$this->assertEmpty($result);
}
/**
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
*/
public function test_get_document_converter_classes_plugin_class_requirements_not_met(): void {
$plugin = $this->getMockBuilder(\core_file_converter_requirements_not_met::class)
->onlyMethods([])
->getMock();
$converter = $this->get_testable_mock(['get_enabled_plugins']);
$converter->method('get_enabled_plugins')->willReturn([
'test_plugin' => get_class($plugin),
]);
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
$this->assertEmpty($result);
}
/**
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
*/
public function test_get_document_converter_classes_plugin_class_met_not_supported(): void {
$plugin = $this->getMockBuilder(\core_file_converter_type_not_supported::class)
->onlyMethods([])
->getMock();
$converter = $this->get_testable_mock(['get_enabled_plugins']);
$converter->method('get_enabled_plugins')->willReturn([
'test_plugin' => get_class($plugin),
]);
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
$this->assertEmpty($result);
}
/**
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
*/
public function test_get_document_converter_classes_plugin_class_met_and_supported(): void {
$plugin = $this->getMockBuilder(\core_file_converter_type_supported::class)
->onlyMethods([])
->getMock();
$classname = get_class($plugin);
$converter = $this->get_testable_mock(['get_enabled_plugins']);
$converter->method('get_enabled_plugins')->willReturn([
'test_plugin' => $classname,
]);
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
$this->assertCount(1, $result);
$this->assertNotFalse(array_search($classname, $result));
}
/**
* Test the can_convert_storedfile_to function with a directory.
*/
public function test_can_convert_storedfile_to_directory(): void {
$converter = $this->get_testable_mock();
// A file with filename '.' is a directory.
$file = $this->get_stored_file('', '.');
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
}
/**
* Test the can_convert_storedfile_to function with an empty file.
*/
public function test_can_convert_storedfile_to_emptyfile(): void {
$converter = $this->get_testable_mock();
// A file with filename '.' is a directory.
$file = $this->get_stored_file('');
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
}
/**
* Test the can_convert_storedfile_to function with a file with indistinguished mimetype.
*/
public function test_can_convert_storedfile_to_no_mimetype(): void {
$converter = $this->get_testable_mock();
// A file with filename '.' is a directory.
$file = $this->get_stored_file('example content', 'example', [
'mimetype' => null,
]);
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
}
/**
* Test the can_convert_storedfile_to function with a file with a known mimetype and extension.
*/
public function test_can_convert_storedfile_to_docx(): void {
$returnvalue = (object) [];
$converter = $this->get_testable_mock([
'can_convert_format_to'
]);
$types = \core_filetypes::get_types();
$file = $this->get_stored_file('example content', 'example.docx', [
'mimetype' => $types['docx']['type'],
]);
$converter->expects($this->once())
->method('can_convert_format_to')
->willReturn($returnvalue);
$result = $converter->can_convert_storedfile_to($file, 'target');
$this->assertEquals($returnvalue, $result);
}
/**
* Test the can_convert_format_to function.
*/
public function test_can_convert_format_to_found(): void {
$converter = $this->get_testable_mock(['get_document_converter_classes']);
$mock = $this->get_mocked_converter();
$converter->method('get_document_converter_classes')
->willReturn([$mock]);
$result = $converter->can_convert_format_to('from', 'to');
$this->assertTrue($result);
}
/**
* Test the can_convert_format_to function.
*/
public function test_can_convert_format_to_not_found(): void {
$converter = $this->get_testable_mock(['get_document_converter_classes']);
$converter->method('get_document_converter_classes')
->willReturn([]);
$result = $converter->can_convert_format_to('from', 'to');
$this->assertFalse($result);
}
/**
* Test the can_convert_storedfile_to function with an empty file.
*/
public function test_poll_conversion_in_progress(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
'get_next_converter',
]);
$converter->method('get_document_converter_classes')->willReturn([]);
$converter->method('get_next_converter')->willReturn(false);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $this->get_testable_conversion([
'get_converter_instance',
]);
$conversion->set_sourcefile($file);
$conversion->set('targetformat', 'target');
$conversion->set('status', conversion::STATUS_IN_PROGRESS);
$conversion->create();
$converterinstance = $this->get_mocked_converter([
'poll_conversion_status',
]);
$converterinstance->expects($this->once())
->method('poll_conversion_status');
$conversion->method('get_converter_instance')->willReturn($converterinstance);
$converter->poll_conversion($conversion);
$this->assertEquals(conversion::STATUS_IN_PROGRESS, $conversion->get('status'));
}
/**
* Test poll_conversion with an in-progress conversion where we are
* unable to instantiate the converter instance.
*/
public function test_poll_conversion_in_progress_fail(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
'get_next_converter',
]);
$converter->method('get_document_converter_classes')->willReturn([]);
$converter->method('get_next_converter')->willReturn(false);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $this->get_testable_conversion([
'get_converter_instance',
]);
$conversion->set_sourcefile($file);
$conversion->set('targetformat', 'target');
$conversion->set('status', conversion::STATUS_IN_PROGRESS);
$conversion->create();
$conversion->method('get_converter_instance')->willReturn(false);
$converter->poll_conversion($conversion);
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
}
/**
* Test the can_convert_storedfile_to function with an empty file.
*/
public function test_poll_conversion_none_supported(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
'get_next_converter',
]);
$converter->method('get_document_converter_classes')->willReturn([]);
$converter->method('get_next_converter')->willReturn(false);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = new conversion(0, (object) [
'sourcefileid' => $file->get_id(),
'targetformat' => 'target',
]);
$conversion->create();
$converter->poll_conversion($conversion);
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
}
/**
* Test the can_convert_storedfile_to function with an empty file.
*/
public function test_poll_conversion_pick_first(): void {
$this->resetAfterTest();
$converterinstance = $this->get_mocked_converter([
'start_document_conversion',
'poll_conversion_status',
]);
$converter = $this->get_testable_mock([
'get_document_converter_classes',
'get_next_converter',
]);
$converter->method('get_document_converter_classes')->willReturn([]);
$converter->method('get_next_converter')->willReturn(get_class($converterinstance));
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $this->get_testable_conversion([
'get_converter_instance',
]);
$conversion->set_sourcefile($file);
$conversion->set('targetformat', 'target');
$conversion->set('status', conversion::STATUS_PENDING);
$conversion->create();
$conversion->method('get_converter_instance')->willReturn($converterinstance);
$converterinstance->expects($this->once())
->method('start_document_conversion');
$converterinstance->expects($this->never())
->method('poll_conversion_status');
$converter->poll_conversion($conversion);
$this->assertEquals(conversion::STATUS_IN_PROGRESS, $conversion->get('status'));
}
/**
* Test the can_convert_storedfile_to function with an empty file.
*/
public function test_poll_conversion_pick_subsequent(): void {
$this->resetAfterTest();
$converterinstance = $this->get_mocked_converter([
'start_document_conversion',
'poll_conversion_status',
]);
$converterinstance2 = $this->get_mocked_converter([
'start_document_conversion',
'poll_conversion_status',
]);
$converter = $this->get_testable_mock([
'get_document_converter_classes',
'get_next_converter',
]);
$converter->method('get_document_converter_classes')->willReturn([]);
$converter->method('get_next_converter')
->will($this->onConsecutiveCalls(
get_class($converterinstance),
get_class($converterinstance2)
));
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $this->get_testable_conversion([
'get_converter_instance',
'get_status',
]);
$conversion->set_sourcefile($file);
$conversion->set('targetformat', 'target');
$conversion->set('status', conversion::STATUS_PENDING);
$conversion->create();
$conversion->method('get_status')
->will($this->onConsecutiveCalls(
// Initial status check.
conversion::STATUS_PENDING,
// Second check to make sure it's still pending after polling.
conversion::STATUS_PENDING,
// First one fails.
conversion::STATUS_FAILED,
// Second one succeeds.
conversion::STATUS_COMPLETE,
// And the final result checked in this unit test.
conversion::STATUS_COMPLETE
));
$conversion->method('get_converter_instance')
->will($this->onConsecutiveCalls(
$converterinstance,
$converterinstance2
));
$converterinstance->expects($this->once())
->method('start_document_conversion');
$converterinstance->expects($this->never())
->method('poll_conversion_status');
$converterinstance2->expects($this->once())
->method('start_document_conversion');
$converterinstance2->expects($this->never())
->method('poll_conversion_status');
$converter->poll_conversion($conversion);
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
}
/**
* Test the start_conversion with a single converter which succeeds.
*/
public function test_start_conversion_one_supported_success(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
]);
$converter->method('get_document_converter_classes')
->willReturn([\core_file_converter_type_successful::class]);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $converter->start_conversion($file, 'target');
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
}
/**
* Test the start_conversion with a single converter which failes.
*/
public function test_start_conversion_one_supported_failure(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
]);
$mock = $this->get_mocked_converter(['start_document_conversion']);
$converter->method('get_document_converter_classes')
->willReturn([\core_file_converter_type_failed::class]);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $converter->start_conversion($file, 'target');
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
}
/**
* Test the start_conversion with two converters - fail, then succeed.
*/
public function test_start_conversion_two_supported(): void {
$this->resetAfterTest();
$converter = $this->get_testable_mock([
'get_document_converter_classes',
]);
$mock = $this->get_mocked_converter(['start_document_conversion']);
$converter->method('get_document_converter_classes')
->willReturn([
\core_file_converter_type_failed::class,
\core_file_converter_type_successful::class,
]);
$file = $this->create_stored_file('example content', 'example', [
'mimetype' => null,
]);
$conversion = $converter->start_conversion($file, 'target');
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
}
/**
* Ensure that get_next_converter returns false when no converters are available.
*/
public function test_get_next_converter_no_converters(): void {
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
$converter = new \core_files\converter();
$result = $rcm->invoke($converter, [], null);
$this->assertFalse($result);
}
/**
* Ensure that get_next_converter returns false when already on the
* only converter.
*/
public function test_get_next_converter_only_converters(): void {
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
$converter = new converter();
$result = $rcm->invoke($converter, ['example'], 'example');
$this->assertFalse($result);
}
/**
* Ensure that get_next_converter returns false when already on the
* last converter.
*/
public function test_get_next_converter_last_converters(): void {
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
$converter = new converter();
$result = $rcm->invoke($converter, ['foo', 'example'], 'example');
$this->assertFalse($result);
}
/**
* Ensure that get_next_converter returns the next vlaue when in a
* current converter.
*/
public function test_get_next_converter_middle_converters(): void {
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
$converter = new converter();
$result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example'], 'bar');
$this->assertEquals('baz', $result);
}
/**
*
* Ensure that get_next_converter returns the next vlaue when in a
* current converter.
*/
public function test_get_next_converter_first(): void {
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
$converter = new converter();
$result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example']);
$this->assertEquals('foo', $result);
}
}
class core_file_converter_requirements_base implements \core_files\converter_interface {
/**
* Whether the plugin is configured and requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return false;
}
/**
* Convert a document to a new format and return a conversion object relating to the conversion in progress.
*
* @param conversion $conversion The file to be converted
* @return conversion
*/
public function start_document_conversion(conversion $conversion) {
}
/**
* Poll an existing conversion for status update.
*
* @param conversion $conversion The file to be converted
* @return conversion
*/
public function poll_conversion_status(conversion $conversion) {
}
/**
* Whether a file conversion can be completed using this converter.
*
* @param string $from The source type
* @param string $to The destination type
* @return bool
*/
public static function supports($from, $to) {
return false;
}
/**
* A list of the supported conversions.
*
* @return string
*/
public function get_supported_conversions() {
return [];
}
}
/**
* Test class for converter support with requirements are not met.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_file_converter_requirements_not_met extends core_file_converter_requirements_base {
}
/**
* Test class for converter support with requirements met and conversion not supported.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_file_converter_type_not_supported extends core_file_converter_requirements_base {
/**
* Whether the plugin is configured and requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return true;
}
}
/**
* Test class for converter support with requirements met and conversion supported.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_file_converter_type_supported extends core_file_converter_requirements_base {
/**
* Whether the plugin is configured and requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return true;
}
/**
* Whether a file conversion can be completed using this converter.
*
* @param string $from The source type
* @param string $to The destination type
* @return bool
*/
public static function supports($from, $to) {
return true;
}
}
/**
* Test class for converter support with requirements met and successful conversion.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_file_converter_type_successful extends core_file_converter_requirements_base {
/**
* Convert a document to a new format and return a conversion object relating to the conversion in progress.
*
* @param conversion $conversion The file to be converted
* @return conversion
*/
public function start_document_conversion(conversion $conversion) {
$conversion->set('status', conversion::STATUS_COMPLETE);
return $conversion;
}
/**
* Whether a file conversion can be completed using this converter.
*
* @param string $from The source type
* @param string $to The destination type
* @return bool
*/
public static function supports($from, $to) {
return true;
}
}
/**
* Test class for converter support with requirements met and failed conversion.
*
* @package core_files
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_file_converter_type_failed extends core_file_converter_requirements_base {
/**
* Whether the plugin is configured and requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return true;
}
/**
* Convert a document to a new format and return a conversion object relating to the conversion in progress.
*
* @param conversion $conversion The file to be converted
* @return conversion
*/
public function start_document_conversion(conversion $conversion) {
$conversion->set('status', conversion::STATUS_FAILED);
return $conversion;
}
/**
* Whether a file conversion can be completed using this converter.
*
* @param string $from The source type
* @param string $to The destination type
* @return bool
*/
public static function supports($from, $to) {
return true;
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_files\external;
use advanced_testcase;
use context_user;
/**
* Unit tests for stored file exporter
*
* @package core_files
* @covers \core_files\external\stored_file_exporter
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stored_file_exporter_test extends advanced_testcase {
/**
* Test exported data structure
*/
public function test_export(): void {
global $PAGE, $USER, $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$contextuser = context_user::instance($USER->id);
$file = get_file_storage()->create_file_from_string([
'contextid' => $contextuser->id,
'userid' => $USER->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => file_get_unused_draft_itemid(),
'filepath' => '/',
'filename' => 'Hi.txt',
], 'Hello');
$exporter = new stored_file_exporter($file, ['context' => $contextuser]);
$export = $exporter->export($PAGE->get_renderer('core'));
$this->assertEquals((object) [
'contextid' => $file->get_contextid(),
'component' => $file->get_component(),
'filearea' => $file->get_filearea(),
'itemid' => $file->get_itemid(),
'filepath' => $file->get_filepath(),
'filename' => $file->get_filename(),
'isdir' => false,
'isimage' => false,
'timemodified' => $file->get_timemodified(),
'timecreated' => $file->get_timecreated(),
'filesize' => $file->get_filesize(),
'author' => $file->get_author(),
'license' => $file->get_license(),
'filenameshort' => $file->get_filename(),
'filesizeformatted' => display_size($file->get_filesize()),
'icon' => 'f/text',
'timecreatedformatted' => userdate($file->get_timecreated()),
'timemodifiedformatted' => userdate($file->get_timemodified()),
'url' => "{$CFG->wwwroot}/pluginfile.php/{$contextuser->id}/user/draft/{$file->get_itemid()}/Hi.txt?forcedownload=1",
], $export);
}
/**
* Data provider for {@see test_export_filenameshort}
*
* @return array[]
*/
public static function export_filenameshort_provider(): array {
return [
// Long filenames (30 characters), with extensions of varying length.
['Lorem ipsum dolor sit amet sit.c', 'Lorem ipsum dolor sit...c'],
['Lorem ipsum dolor sit amet sit.txt', 'Lorem ipsum dolor s...txt'],
['Lorem ipsum dolor sit amet sit.docx', 'Lorem ipsum dolor ...docx'],
// Multi-byte filenames.
['Мазитов А.З. практика тусур.py', 'Мазитов А.З. практик...py'],
];
}
/**
* Test exporting shortened filename
*
* @param string $filename
* @param string $expected
*
* @dataProvider export_filenameshort_provider
*/
public function test_export_filenameshort(string $filename, string $expected): void {
global $PAGE, $USER;
$this->resetAfterTest();
$this->setAdminUser();
$contextuser = context_user::instance($USER->id);
$file = get_file_storage()->create_file_from_string([
'contextid' => $contextuser->id,
'userid' => $USER->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => file_get_unused_draft_itemid(),
'filepath' => '/',
'filename' => $filename,
], 'Hello');
$exporter = new stored_file_exporter($file, ['context' => $contextuser]);
$export = $exporter->export($PAGE->get_renderer('core'));
$this->assertEquals($expected, $export->filenameshort);
}
}
+392
View File
@@ -0,0 +1,392 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_files;
use core_external\external_api;
use core_files\external\delete\draft;
use core_files\external\get\unused_draft;
use core_files_external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/files/externallib.php');
/**
* PHPunit tests for external files API.
*
* @package core_files
* @category external
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.6
*/
class externallib_test extends \advanced_testcase {
/*
* Test core_files_external::upload().
*/
public function test_upload(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$context = \context_user::instance($USER->id);
$contextid = $context->id;
$component = "user";
$filearea = "draft";
$itemid = 0;
$filepath = "/";
$filename = "Simple.txt";
$filecontent = base64_encode("Let us create a nice simple file");
$contextlevel = null;
$instanceid = null;
$browser = get_file_browser();
// Make sure no file exists.
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertEmpty($file);
// Call the api to create a file.
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
$filename, $filecontent, $contextlevel, $instanceid);
$fileinfo = external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
// Get the created draft item id.
$itemid = $fileinfo['itemid'];
// Make sure the file was created.
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
// Make sure no file exists.
$filename = "Simple2.txt";
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertEmpty($file);
// Call the api to create a file.
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid,
$filepath, $filename, $filecontent, $contextlevel, $instanceid);
$fileinfo = external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
// Let us try creating a file using contextlevel and instance id.
$filename = "Simple5.txt";
$contextid = 0;
$contextlevel = "user";
$instanceid = $USER->id;
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertEmpty($file);
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
$filename, $filecontent, $contextlevel, $instanceid);
$fileinfo = external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
// Make sure the same file cannot be created again.
$this->expectException("moodle_exception");
core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
$filename, $filecontent, $contextlevel, $instanceid);
}
/*
* Make sure only user component is allowed in core_files_external::upload().
*/
public function test_upload_param_component(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$context = \context_user::instance($USER->id);
$contextid = $context->id;
$component = "backup";
$filearea = "draft";
$itemid = 0;
$filepath = "/";
$filename = "Simple3.txt";
$filecontent = base64_encode("Let us create a nice simple file");
$contextlevel = null;
$instanceid = null;
// Make sure exception is thrown.
$this->expectException("coding_exception");
core_files_external::upload($contextid, $component, $filearea, $itemid,
$filepath, $filename, $filecontent, $contextlevel, $instanceid);
}
/*
* Make sure only draft areas are allowed in core_files_external::upload().
*/
public function test_upload_param_area(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$context = \context_user::instance($USER->id);
$contextid = $context->id;
$component = "user";
$filearea = "draft";
$itemid = file_get_unused_draft_itemid();
$filepath = "/";
$filename = "Simple4.txt";
$filecontent = base64_encode("Let us create a nice simple file");
$contextlevel = null;
$instanceid = null;
// Make sure the file is created.
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent,
'user', $USER->id);
$fileinfo = external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
$browser = get_file_browser();
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
}
/**
* Test getting a list of files with and without a context ID.
*/
public function test_get_files(): void {
global $USER, $DB;
$this->resetAfterTest();
// Set the current user to be the administrator.
$this->setAdminUser();
$USER->email = 'test@example.com';
// Create a course.
$course = $this->getDataGenerator()->create_course();
$record = new \stdClass();
$record->course = $course->id;
$record->name = "Mod data upload test";
$record->intro = "Some intro of some sort";
// Create a database module.
$module = $this->getDataGenerator()->create_module('data', $record);
// Create a new field in the database activity.
$field = data_get_field_new('file', $module);
// Add more detail about the field.
$fielddetail = new \stdClass();
$fielddetail->d = $module->id;
$fielddetail->mode = 'add';
$fielddetail->type = 'file';
$fielddetail->sesskey = sesskey();
$fielddetail->name = 'Upload file';
$fielddetail->description = 'Some description';
$fielddetail->param3 = '0';
$field->define_field($fielddetail);
$field->insert_field();
$recordid = data_add_record($module);
// File information for the database module record.
$datacontent = array();
$datacontent['fieldid'] = $field->field->id;
$datacontent['recordid'] = $recordid;
$datacontent['content'] = 'Simple4.txt';
// Insert the information about the file.
$contentid = $DB->insert_record('data_content', $datacontent);
// Required information for uploading a file.
$context = \context_module::instance($module->cmid);
$usercontext = \context_user::instance($USER->id);
$component = 'mod_data';
$filearea = 'content';
$itemid = $contentid;
$filename = $datacontent['content'];
$filecontent = base64_encode("Let us create a nice simple file.");
$filerecord = array();
$filerecord['contextid'] = $context->id;
$filerecord['component'] = $component;
$filerecord['filearea'] = $filearea;
$filerecord['itemid'] = $itemid;
$filerecord['filepath'] = '/';
$filerecord['filename'] = $filename;
// Create an area to upload the file.
$fs = get_file_storage();
// Create a file from the string that we made earlier.
$file = $fs->create_file_from_string($filerecord, $filecontent);
$timemodified = $file->get_timemodified();
$timecreated = $file->get_timemodified();
$filesize = $file->get_filesize();
// Use the web service function to return the information about the file that we just uploaded.
// The first time is with a valid context ID.
$filename = '';
$testfilelisting = core_files_external::get_files($context->id, $component, $filearea, $itemid, '/', $filename);
$testfilelisting = external_api::clean_returnvalue(core_files_external::get_files_returns(), $testfilelisting);
// With the information that we have provided we should get an object exactly like the one below.
$coursecontext = \context_course::instance($course->id);
$testdata = array();
$testdata['parents'] = array();
$testdata['parents']['0'] = array('contextid' => 1,
'component' => null,
'filearea' => null,
'itemid' => null,
'filepath' => null,
'filename' => 'System');
$testdata['parents']['1'] = array('contextid' => 3,
'component' => null,
'filearea' => null,
'itemid' => null,
'filepath' => null,
'filename' => get_string('defaultcategoryname'));
$testdata['parents']['2'] = array('contextid' => $coursecontext->id,
'component' => null,
'filearea' => null,
'itemid' => null,
'filepath' => null,
'filename' => 'Test course 1');
$testdata['parents']['3'] = array('contextid' => $context->id,
'component' => null,
'filearea' => null,
'itemid' => null,
'filepath' => null,
'filename' => 'Mod data upload test (Database)');
$testdata['parents']['4'] = array('contextid' => $context->id,
'component' => 'mod_data',
'filearea' => 'content',
'itemid' => null,
'filepath' => null,
'filename' => 'Fields');
$testdata['files'] = array();
$testdata['files']['0'] = array('contextid' => $context->id,
'component' => 'mod_data',
'filearea' => 'content',
'itemid' => $itemid,
'filepath' => '/',
'filename' => 'Simple4.txt',
'url' => 'https://www.example.com/moodle/pluginfile.php/'.$context->id.'/mod_data/content/'.$itemid.'/Simple4.txt',
'isdir' => false,
'timemodified' => $timemodified,
'timecreated' => $timecreated,
'filesize' => $filesize,
'author' => null,
'license' => null
);
// Make sure that they are the same.
$this->assertEquals($testdata, $testfilelisting);
// Try again but without the context. Minus one signals the function to use other variables to obtain the context.
$nocontext = -1;
$modified = 0;
// Context level and instance ID are used to determine what the context is.
$contextlevel = 'module';
$instanceid = $module->cmid;
$testfilelisting = core_files_external::get_files($nocontext, $component, $filearea, $itemid, '/', $filename, $modified, $contextlevel, $instanceid);
$testfilelisting = external_api::clean_returnvalue(core_files_external::get_files_returns(), $testfilelisting);
$this->assertEquals($testfilelisting, $testdata);
}
/**
* Test delete draft files
*/
public function test_delete_draft_files(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
// Add files to user draft area.
$draftitemid = file_get_unused_draft_itemid();
$context = \context_user::instance($USER->id);
$filerecordinline = array(
'contextid' => $context->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftitemid,
'filepath' => '/',
'filename' => 'faketxt.txt',
);
$fs = get_file_storage();
$fs->create_file_from_string($filerecordinline, 'fake txt contents 1.');
// Now create a folder with a file inside.
$fs->create_directory($context->id, 'user', 'draft', $draftitemid, '/fakefolder/');
$filerecordinline['filepath'] = '/fakefolder/';
$filerecordinline['filename'] = 'fakeimage.png';
$fs->create_file_from_string($filerecordinline, 'img...');
// Check two files were created (one file and one directory).
$files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/', '');
$files = external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
$this->assertCount(2, $files['files']);
// Check the folder has one file.
$files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/fakefolder/', '');
$files = external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
$this->assertCount(1, $files['files']);
// Delete a file and a folder.
$filestodelete = [
['filepath' => '/', 'filename' => 'faketxt.txt'],
['filepath' => '/fakefolder/', 'filename' => ''],
];
$paths = draft::execute($draftitemid, $filestodelete);
$paths = external_api::clean_returnvalue(draft::execute_returns(), $paths);
// Check everything was deleted.
$files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/', '');
$files = external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
$this->assertCount(0, $files['files']);
}
/**
* Test get_unused_draft_itemid.
*/
public function test_get_unused_draft_itemid(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
// Add files to user draft area.
$result = unused_draft::execute();
$result = external_api::clean_returnvalue(unused_draft::execute_returns(), $result);
$filerecordinline = [
'contextid' => $result['contextid'],
'component' => $result['component'],
'filearea' => $result['filearea'],
'itemid' => $result['itemid'],
'filepath' => '/',
'filename' => 'faketxt.txt',
];
$fs = get_file_storage();
$fs->create_file_from_string($filerecordinline, 'fake txt contents 1.');
// Now create a folder with a file inside.
$fs->create_directory($result['contextid'], $result['component'], $result['filearea'], $result['itemid'], '/fakefolder/');
$filerecordinline['filepath'] = '/fakefolder/';
$filerecordinline['filename'] = 'fakeimage.png';
$fs->create_file_from_string($filerecordinline, 'img...');
$context = \context_user::instance($USER->id);
// Check two files were created (one file and one directory).
$files = core_files_external::get_files($context->id, 'user', 'draft', $result['itemid'], '/', '');
$files = external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
$this->assertCount(2, $files['files']);
}
}
+1
View File
@@ -0,0 +1 @@
Hey, this is an awesome text file. Hello! :)
+1
View File
@@ -0,0 +1 @@
empty file for testing purposes
@@ -0,0 +1,159 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_files\local\archive_writer;
use advanced_testcase;
use context_module;
use core_files\archive_writer;
use ZipArchive;
/**
* Unit tests for \core_files\local\archive_writer\zip_writer.
*
* @package core_files
* @category test
* @copyright 2020 Mark Nelson <mdjnelson@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_files\local\archive_writer\zip_writer
*/
class zip_writer_test extends advanced_testcase {
/**
* Test add_file_from_filepath().
*/
public function test_add_file_from_filepath(): void {
global $CFG;
$pathtofileinzip = '/some/made/up/name.txt';
$filetoadd = $CFG->dirroot . '/files/tests/fixtures/awesome_file.txt';
$zipwriter = archive_writer::get_file_writer('test.zip', archive_writer::ZIP_WRITER);
$zipwriter->add_file_from_filepath($pathtofileinzip, $filetoadd);
$zipwriter->finish();
$pathtozip = $zipwriter->get_path_to_zip();
$zip = new ZipArchive();
$opened = $zip->open($pathtozip);
$this->assertTrue($opened);
// Filename that has been sanitized by Zipstream.
$pathtofileinzip = ltrim($pathtofileinzip, '/');
$this->assertEquals("Hey, this is an awesome text file. Hello! :)", $zip->getFromName($pathtofileinzip));
}
/**
* Test add_file_from_string().
*/
public function test_add_file_from_string(): void {
$pathtofileinzip = "/path/to/my/awesome/file.txt";
$mycontent = "This is some real awesome content, ya dig?";
$zipwriter = archive_writer::get_file_writer('test.zip', archive_writer::ZIP_WRITER);
$zipwriter->add_file_from_string($pathtofileinzip, $mycontent);
$zipwriter->finish();
$pathtozip = $zipwriter->get_path_to_zip();
$zip = new ZipArchive();
$opened = $zip->open($pathtozip);
$this->assertTrue($opened);
// Filename that has been sanitized by Zipstream.
$pathtofileinzip = ltrim($pathtofileinzip, '/');
$this->assertEquals($mycontent, $zip->getFromName($pathtofileinzip));
}
/**
* Test add_file_from_stream().
*/
public function test_add_file_from_stream(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
// Add a file to the intro.
$filerecord = [
'contextid' => context_module::instance($assign->cmid)->id,
'component' => 'mod_assign',
'filearea' => 'intro',
'itemid' => 0,
'filepath' => '/',
'filename' => 'fileintro.txt',
];
$fs = get_file_storage();
$storedfile = $fs->create_file_from_string($filerecord, 'Contents for the assignment, yeow!');
$pathtofileinzip = $storedfile->get_filepath() . $storedfile->get_filename();
$zipwriter = archive_writer::get_file_writer('test.zip', archive_writer::ZIP_WRITER);
$zipwriter->add_file_from_stream($pathtofileinzip, $storedfile->get_content_file_handle());
$zipwriter->finish();
$pathtozip = $zipwriter->get_path_to_zip();
$zip = new ZipArchive();
$opened = $zip->open($pathtozip);
$this->assertTrue($opened);
// Filename that has been sanitized by Zipstream.
$pathtofileinzip = ltrim($pathtofileinzip, '/');
$this->assertEquals($storedfile->get_content(), $zip->getFromName($pathtofileinzip));
}
/**
* Test add_file_from_stored_file().
*/
public function test_add_file_from_stored_file(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
// Add a file to the intro.
$filerecord = [
'contextid' => context_module::instance($assign->cmid)->id,
'component' => 'mod_assign',
'filearea' => 'intro',
'itemid' => 0,
'filepath' => '/',
'filename' => 'fileintro.txt',
];
$fs = get_file_storage();
$storedfile = $fs->create_file_from_string($filerecord, 'Contents for the assignment, yeow!');
$pathtofileinzip = $storedfile->get_filepath() . $storedfile->get_filename();
$zipwriter = archive_writer::get_file_writer('test.zip', archive_writer::ZIP_WRITER);
$zipwriter->add_file_from_stored_file($pathtofileinzip, $storedfile);
$zipwriter->finish();
$pathtozip = $zipwriter->get_path_to_zip();
$zip = new ZipArchive();
$opened = $zip->open($pathtozip);
$this->assertTrue($opened);
// Filename that has been sanitized by Zipstream.
$pathtofileinzip = ltrim($pathtofileinzip, '/');
$this->assertEquals($storedfile->get_content(), $zip->getFromName($pathtofileinzip));
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for unit tests for core_cohort.
*
* @package core_files
* @category test
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_files\privacy;
defined('MOODLE_INTERNAL') || die();
use core_files\privacy\provider;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_userlist;
/**
* Unit tests for files\classes\privacy\provider.php
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
/**
* Test getting the context for the user ID related to this plugin.
*/
public function test_get_contexts_for_userid(): void {
$this->resetAfterTest();
// Create a user.
$user = $this->getDataGenerator()->create_user();
$userctx = \context_user::instance($user->id);
create_user_key('core_files', $user->id);
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(1, (array) $contextlist->get_contextids());
$this->assertContainsEquals($userctx->id, $contextlist->get_contextids());
}
/**
* Test that data is exported correctly for this plugin.
*/
public function test_export_user_data(): void {
global $DB;
$this->resetAfterTest();
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$keyvalue = get_user_key('core_files', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Validate exported data.
$this->setUser($user);
$writer = writer::with_context($usercontext);
$this->assertFalse($writer->has_any_data());
$this->export_context_data_for_user($user->id, $usercontext, 'core_files');
$subcontext = [
get_string('files')
];
$userkeydata = $writer->get_related_data($subcontext, 'userkeys');
$this->assertCount(1, $userkeydata->keys);
$this->assertEquals($key->script, reset($userkeydata->keys)->script);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$this->resetAfterTest();
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
create_user_key('core_files', $user->id);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'core_files']);
$this->assertEquals(1, $count);
// Delete data.
provider::delete_data_for_all_users_in_context($usercontext);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'core_files']);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
$this->resetAfterTest();
// Create a user.
$user = $this->getDataGenerator()->create_user();
create_user_key('core_files', $user->id);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'core_files']);
$this->assertEquals(1, $count);
// Delete data.
$contextlist = provider::get_contexts_for_userid($user->id);
$approvedcontextlist = new approved_contextlist($user, 'core_files', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'core_files']);
$this->assertEquals(0, $count);
}
/**
* Test that only users within a course context are fetched.
*/
public function test_get_users_in_context(): void {
$this->resetAfterTest();
$component = 'core_files';
// Create a user.
$user = $this->getDataGenerator()->create_user();
$userctx = \context_user::instance($user->id);
$userlist = new \core_privacy\local\request\userlist($userctx, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
create_user_key('core_files', $user->id);
// The list of users within the userctx context should contain user.
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$user->id];
$actual = $userlist->get_userids();
$this->assertEquals($expected, $actual);
// The list of users within contexts different than user should be empty.
$systemctx = \context_system::instance();
$userlist = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
$this->resetAfterTest();
$component = 'core_files';
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
$userctx1 = \context_user::instance($user1->id);
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
$userctx2 = \context_user::instance($user2->id);
create_user_key('core_files', $user1->id);
create_user_key('core_files', $user2->id);
$userlist1 = new \core_privacy\local\request\userlist($userctx1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($userctx2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// Convert $userlist1 into an approved_contextlist.
$approvedlist1 = new approved_userlist($userctx1, $component, $userlist1->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist1);
// Re-fetch users in userctx1.
$userlist1 = new \core_privacy\local\request\userlist($userctx1, $component);
provider::get_users_in_context($userlist1);
// The user data in coursecategoryctx should be deleted.
$this->assertCount(0, $userlist1);
// Re-fetch users in userctx2.
$userlist2 = new \core_privacy\local\request\userlist($userctx2, $component);
provider::get_users_in_context($userlist2);
// The user data in userctx2 should be still present.
$this->assertCount(1, $userlist2);
// Convert $userlist2 into an approved_contextlist in the system context.
$systemctx = \context_system::instance();
$approvedlist3 = new approved_userlist($systemctx, $component, $userlist2->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist3);
// Re-fetch users in userctx2.
$userlist3 = new \core_privacy\local\request\userlist($userctx2, $component);
provider::get_users_in_context($userlist3);
// The user data in userctx2 should not be deleted.
$this->assertCount(1, $userlist3);
}
}
@@ -0,0 +1,393 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_files\reportbuilder\datasource;
use core\context\{course, coursecat, user};
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\filters\{boolean_select, date, filesize, select, text};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for files datasource
*
* @package core_files
* @covers \core_files\reportbuilder\datasource\files
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class files_test extends core_reportbuilder_testcase {
/**
* Test default datasource
*/
public function test_datasource_default(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$user = $this->getDataGenerator()->create_user();
$usercontext = user::instance($user->id);
$this->setUser($user);
$this->generate_test_files($coursecontext);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
$content = $this->filter_custom_report_content($content, static function(array $row): bool {
return $row['c0_ctxid'] !== 'System';
});
$this->assertCount(2, $content);
// Default columns are context, user, name, type, size, time created. Sorted by context and time created.
[$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[0]);
$this->assertEquals($coursecontext->get_context_name(), $contextname);
$this->assertEquals(fullname($user), $userfullname);
$this->assertEquals('Hello.txt', $filename);
$this->assertEquals('Text file', $mimetype);
$this->assertEquals("5\xc2\xa0bytes", $filesize);
$this->assertNotEmpty($timecreated);
[$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[1]);
$this->assertEquals($usercontext->get_context_name(), $contextname);
$this->assertEquals(fullname($user), $userfullname);
$this->assertEquals('Hello.txt', $filename);
$this->assertEquals('Text file', $mimetype);
$this->assertEquals("5\xc2\xa0bytes", $filesize);
$this->assertNotEmpty($timecreated);
}
/**
* Test datasource columns that aren't added by default
*/
public function test_datasource_non_default_columns(): void {
global $OUTPUT;
$this->resetAfterTest();
$category = $this->getDataGenerator()->create_category();
$categorycontext = coursecat::instance($category->id);
$course = $this->getDataGenerator()->create_course(['category' => $category->id]);
$coursecontext = course::instance($course->id);
$user = $this->getDataGenerator()->create_user();
$usercontext = user::instance($user->id);
$this->setUser($user);
$draftitemid = $this->generate_test_files($coursecontext);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]);
// Consistent order, sorted by context and content hash.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link',
'sortenabled' => 1, 'sortorder' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:level']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:path']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:parent']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:icon']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:path']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:author']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:license']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:contenthash',
'sortenabled' => 1, 'sortorder' => 2]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:component']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:area']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:itemid']);
$content = $this->get_custom_report_content($report->get('id'));
$content = $this->filter_custom_report_content($content, static function(array $row): bool {
return stripos($row['c0_ctxid'], 'System') === false;
});
// There should be two entries (directory & file) for each context.
$this->assertEquals([
[
"<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>",
$coursecontext->get_context_name(),
'Course',
$coursecontext->path,
$categorycontext->get_context_name(),
'<img class="icon iconsize-medium" alt="Directory" title="Directory" src="' .
$OUTPUT->image_url('f/folder')->out() . '" />',
'/',
null,
'',
'da39a3ee5e6b4b0d3255bfef95601890afd80709',
'course',
'summary',
0,
],
[
"<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>",
$coursecontext->get_context_name(),
'Course',
$coursecontext->path,
$categorycontext->get_context_name(),
'<img class="icon iconsize-medium" alt="Text file" title="Text file" src="' .
$OUTPUT->image_url('f/text')->out() . '" />',
'/',
null,
'',
'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0',
'course',
'summary',
0,
],
[
"<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>",
$usercontext->get_context_name(),
'User',
$usercontext->path,
'System',
'<img class="icon iconsize-medium" alt="Directory" title="Directory" src="' .
$OUTPUT->image_url('f/folder')->out() . '" />',
'/',
null,
'',
'da39a3ee5e6b4b0d3255bfef95601890afd80709',
'user',
'draft',
$draftitemid,
],
[
"<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>",
$usercontext->get_context_name(),
'User',
$usercontext->path,
'System',
'<img class="icon iconsize-medium" alt="Text file" title="Text file" src="' .
$OUTPUT->image_url('f/text')->out() . '" />',
'/',
null,
'',
'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0',
'user',
'draft',
$draftitemid,
],
], array_map('array_values', $content));
}
/**
* Data provider for {@see test_datasource_filters}
*
* @return array[]
*/
public function datasource_filters_provider(): array {
return [
// File.
'Filter directory' => ['file:directory', [
'file:directory_operator' => boolean_select::CHECKED,
], 2],
'Filter draft' => ['file:draft', [
'file:draft_operator' => boolean_select::CHECKED,
], 2],
'Filter name' => ['file:name', [
'file:name_operator' => text::IS_EQUAL_TO,
'file:name_value' => 'Hello.txt',
], 2],
'Filter size' => ['file:size', [
'file:size_operator' => filesize::GREATER_THAN,
'file:size_value1' => 2,
'file:size_unit' => filesize::SIZE_UNIT_BYTE,
], 2],
'Filter type' => ['file:type', [
'file:type_operator' => select::EQUAL_TO,
'file:type_value' => 'text/plain',
], 2],
'Filter type (non match)' => ['file:type', [
'file:type_operator' => select::EQUAL_TO,
'file:type_value' => 'image/png',
], 0],
'Filter license' => ['file:license', [
'file:license_operator' => select::EQUAL_TO,
'file:license_value' => 'unknown',
], 4],
'Filter license (non match)' => ['file:license', [
'file:license_operator' => select::EQUAL_TO,
'file:license_value' => 'public',
], 0],
'Filter content hash' => ['file:contenthash', [
'file:contenthash_operator' => text::IS_EQUAL_TO,
'file:contenthash_value' => 'f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0',
], 2],
'Filter content hash (no match)' => ['file:contenthash', [
'file:contenthash_operator' => text::IS_EQUAL_TO,
'file:contenthash_value' => 'f00f',
], 0],
'Filter time created' => ['file:timecreated', [
'file:timecreated_operator' => date::DATE_RANGE,
'file:timecreated_from' => 1622502000,
], 4],
'Filter time created (non match)' => ['file:timecreated', [
'file:timecreated_operator' => date::DATE_RANGE,
'file:timecreated_to' => 1622502000,
], 0],
// Context.
'Context level' => ['context:level', [
'context:level_operator' => select::EQUAL_TO,
'context:level_value' => CONTEXT_COURSE,
], 2],
'Context level (no match)' => ['context:level', [
'context:level_operator' => select::EQUAL_TO,
'context:level_value' => CONTEXT_BLOCK,
], 0],
'Context path' => ['context:path', [
'context:path_operator' => text::STARTS_WITH,
'context:path_value' => '/1/',
], 4],
'Context path (no match)' => ['context:path', [
'context:path_operator' => text::STARTS_WITH,
'context:path_value' => '/1/2/3/',
], 0],
// User.
'Filter user' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'alfie',
], 4],
'Filter user (no match)' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'lionel',
], 0],
];
}
/**
* Test datasource filters
*
* @param string $filtername
* @param array $filtervalues
* @param int $expectmatchcount
*
* @dataProvider datasource_filters_provider
*/
public function test_datasource_filters(
string $filtername,
array $filtervalues,
int $expectmatchcount
): void {
$this->resetAfterTest();
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user(['username' => 'alfie']);
$this->setUser($user);
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$this->generate_test_files($coursecontext);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create report containing single column, and given filter.
$report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']);
// Add filter, set it's values.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
$content = $this->filter_custom_report_content($content, static function(array $row): bool {
return stripos($row['c0_ctxid'], 'System') === false;
});
$this->assertCount($expectmatchcount, $content);
}
/**
* Stress test datasource
*
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
*/
public function test_stress_datasource(): void {
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$this->generate_test_files($coursecontext);
$this->datasource_stress_test_columns(files::class);
$this->datasource_stress_test_columns_aggregation(files::class);
$this->datasource_stress_test_conditions(files::class, 'file:path');
}
/**
* Ensuring report content only includes files we have explicitly created within the test
*
* @param array $content
* @param callable $callback
* @return array
*/
protected function filter_custom_report_content(array $content, callable $callback): array {
$content = array_filter($content, $callback);
return array_values($content);
}
/**
* Helper method to generate some test files (a user draft and course summary file) for reporting on
*
* @param course $context
* @return int Draft item ID
*/
protected function generate_test_files(course $context): int {
global $USER;
$draftitemid = file_get_unused_draft_itemid();
// Populate user draft.
get_file_storage()->create_file_from_string([
'contextid' => user::instance($USER->id)->id,
'userid' => $USER->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftitemid,
'filepath' => '/',
'filename' => 'Hello.txt',
], 'Hello');
// Save draft to course summary file area.
file_save_draft_area_files($draftitemid, $context->id, 'course', 'summary', 0);
return $draftitemid;
}
}