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
@@ -0,0 +1,94 @@
@editor @editor_atto @atto
Feature: Atto Autosave
To reduce frustration, atto should save drafts of my work.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | editingteacher |
And I log in as "admin"
And I navigate to "Plugins > Text editors > Atto HTML editor > Atto toolbar settings" in site administration
And I set the field "Autosave frequency" to "3"
And I set the field with xpath "//select[@name='s_editor_atto_autosavefrequency[u]']" to "seconds"
And I click on "Save changes" "button"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the field "Course summary format" to "1"
And I click on "Save and display" "button"
And I log out
@javascript
Scenario: Restore a draft
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "This is my draft"
# Wait for the autosave
And I wait "5" seconds
And I log out
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
# Wait for the autorestore
And I wait "2" seconds
Then I should see "This is my draft"
@javascript
Scenario: Do not restore a draft if files have been modified
Given the following "user private file" exists:
| user | teacher2 |
| filepath | lib/editor/atto/tests/fixtures/moodle-logo.png |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "This is my draft"
# Wait for the autosave
And I wait "5" seconds
And I log out
And I am on the "Course 1" course page logged in as teacher2
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "<p>Image test</p>"
And I select the text in the "Course summary" Atto editor
And I click on "Insert or edit image" "button"
And I click on "Browse repositories..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "moodle-logo.png" "link"
And I click on "Select this file" "button"
And I set the field "Describe this image" to "It's the Moodle"
# Wait for the page to "settle".
And I wait until the page is ready
And I click on "Save image" "button"
And I click on "Save and display" "button"
And I log out
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
Then I should not see "This is my draft"
@javascript
Scenario: Do not restore a draft if text has been modified
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "This is my draft"
# Wait for the autosave
And I wait "5" seconds
And I log out
And I log in as "teacher2"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "Modified text"
And I click on "Save and display" "button"
And I log out
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
Then I should not see "This is my draft"
And I should see "Modified text"
@@ -0,0 +1,108 @@
<?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/>.
/**
* Atto custom steps definitions.
*
* @package editor_atto
* @category test
* @copyright 2014 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../behat/behat_base.php');
require_once(__DIR__ . '/../../../../behat/classes/settable_editor.php');
/**
* Steps definitions to deal with the atto text editor
*
* @package editor_atto
* @category test
* @copyright 2014 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_editor_atto extends behat_base implements \core_behat\settable_editor {
/**
* Set the value for the editor.
*
* @param string $editorid
* @param string $value
*/
public function set_editor_value(string $editorid, string $value): void {
$js = <<<EOF
(function() {
const editableEditor = document.getElementById("{$editorid}editable");
if (editableEditor && editableEditor.classList.contains('editor_atto_content')) {
editableEditor.innerHTML = "{$value}";
}
const editor = document.getElementById("{$editorid}");
if (editor) {
editor.value = "{$value}";
}
})();
EOF;
$this->execute_script($js);
}
/**
* Select the text in an Atto field.
*
* @Given /^I select the text in the "([^"]*)" Atto editor$/
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $field
* @return void
*/
public function select_the_text_in_the_atto_editor($fieldlocator) {
if (!$this->running_javascript()) {
throw new coding_exception('Selecting text requires javascript.');
}
// We delegate to behat_form_field class, it will
// guess the type properly.
$field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
if (!method_exists($field, 'select_text')) {
throw new coding_exception('Field does not support the select_text function.');
}
$field->select_text();
}
/**
* Ensure that the Atto editor is used for all tests using the editor_atto, or atto_* tags.
*
* This ensures, whatever the default editor, that the Atto editor is used for these tests.
*
* @BeforeScenario
* @param BeforeScenarioScope $scope The Behat Scope
*/
public function set_default_editor_flag(BeforeScenarioScope $scope): void {
// This only applies to a scenario which matches the editor_atto, or an atto subplugin.
$callback = function (string $tag): bool {
return $tag === 'editor_atto' || substr($tag, 0, 5) === 'atto_';
};
if (!self::scope_tags_match($scope, $callback)) {
return;
}
$this->execute('behat_general::the_default_editor_is_set_to', ['atto']);
}
}
+169
View File
@@ -0,0 +1,169 @@
@editor @editor_atto @atto @editor_moodleform
Feature: Atto HTML cleanup.
In order to test html cleaning functionality, I write in a HTML atto text field.
@javascript
Scenario: Extra UL close and orphan LI items
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I set the field "Description" to multiline:
"""
<li>A</li>
<li>B</li>
</ol>
<ul>
<li>C</li>
</ul></ul>
<li class="someclass ul UL">D</li>
<li>E</li>
"""
And I click on "HTML" "button"
Then the field "Description" matches multiline:
"""
<ol><li>A</li>
<li>B</li>
</ol>
<ul>
<li>C</li>
</ul>
<ul><li class="someclass ul UL">D</li>
<li>E</li></ul>
"""
@javascript
Scenario: Missing LI close tags, extra closing OL, missing closing UL tag
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I set the field "Description" to multiline:
"""
<div class="ol"><ol>
<li>A</li>
<li>B
</ol></div>
<ul>
<li>C
<li>D</li>
</ol>
"""
And I click on "HTML" "button"
Then the field "Description" matches multiline:
"""
<div class="ol"><ol>
<li>A</li>
<li>B
</li></ol></div>
<ul>
<li>C
</li><li>D</li></ul>
"""
@javascript
Scenario: Missing beginning OL tag, empty LI close tag
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I set the field "Description" to multiline:
"""
<p>Before</p>
<li>A</li></li>
<li>B</li>
</ol>
<p>After</p>
<ul data-info="UL ul OL ol">
<ul>
C</li>
<li>D</li>
<li>E
</ul>
</ul><ul>
<p>After 2</p>
"""
And I click on "HTML" "button"
Then the field "Description" matches multiline:
"""
<p>Before</p>
<ol><li>A</li>
<li>B</li>
</ol>
<p>After</p>
<ul data-info="UL ul OL ol">
<ul><li>
C</li>
<li>D</li>
<li>E
</li></ul>
</ul>
<p>After 2</p>
"""
@javascript
Scenario: Random close LI tag, extra LI open tag, missing OL tag
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I set the field "Description" to multiline:
"""
<p>Before</p></li><ul>
<ul>
<li>A</li>
B</li>
<li>C</li>
<ol>
<li>D</li>
<li>E
<p>After</p>
"""
And I click on "HTML" "button"
Then the field "Description" matches multiline:
"""
<p>Before</p>
<ul>
<li>A</li><li>
B</li>
<li>C</li></ul>
<ol>
<li>D</li></ol>
E
<p>After</p>
"""
@javascript
Scenario: Missing opening LI tags, missing closing UL tag
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I set the field "Description" to multiline:
"""
<li>Before</li>
<ul>
<li>A</li>
B</li>
<ol>
1</li>
</ol>
<li>C
<li>D</li>
<p>After</p>
"""
And I click on "HTML" "button"
Then the field "Description" matches multiline:
"""
<ul><li>Before</li></ul>
<ul>
<li>A</li><li>
B</li>
<ol><li>
1</li>
</ol>
<li>C
</li><li>D</li></ul>
<p>After</p>
"""
@@ -0,0 +1,26 @@
@editor @editor_atto @atto
Feature: Atto editor with customised toolbar
In order to develop plugins that use Atto for specialised purposes
As a developer
I need to be able to configure Atto toolbar per-instance to include different plugins
Background:
# Get to the fixture page.
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| label | L1 | <a href="../lib/editor/atto/tests/fixtures/custom_toolbar_example.php">FixtureLink</a> | C1 | label1 |
When I log in as "admin"
And I am on "Course 1" course homepage
And I click on "FixtureLink" "link" in the "region-main" "region"
@javascript
Scenario: Confirm that both editors have different toolbars but still function
Then ".atto_link_button" "css_element" should exist in the ".normaldiv" "css_element"
And ".atto_link_button" "css_element" should not exist in the ".specialdiv" "css_element"
And ".atto_bold_button" "css_element" should exist in the ".normaldiv" "css_element"
And ".atto_italic_button" "css_element" should exist in the ".normaldiv" "css_element"
And ".atto_bold_button" "css_element" should exist in the ".specialdiv" "css_element"
And ".atto_italic_button" "css_element" should exist in the ".specialdiv" "css_element"
@@ -0,0 +1,49 @@
@editor @editor_atto @atto
Feature: Add text direction and alignment
In order to generate a content that can be displayed in the proper direction to everyone
As a user
I should see the Atto editor with explicit direction and alignment being set
Background:
Given the following "user preferences" exist:
| user | preference | value |
| admin | htmleditor | atto |
And I log in as "admin"
And I navigate to "Plugins > Text editors > Atto HTML editor > Atto toolbar settings" in site administration
And I set the field "Toolbar config" to multiline:
"""
collapse = collapse
style1 = title, bold, italic
list = unorderedlist, orderedlist
links = link
files = image, media, recordrtc, managefiles, h5p
style2 = underline, strike, subscript, superscript
align = align,rtl
indent = indent
insert = equation, charmap, table, clear
undo = undo
accessibility = accessibilitychecker, accessibilityhelper
other = html
"""
And I press "Save changes"
And I log out
@javascript
Scenario Outline: Atto should apply user's direction and alignment by default
Given the following "courses" exist:
| fullname | shortname | summary | summaryformat |
| Course 1 | C1 | | 1 |
And the following "language customisations" exist:
| component | stringid | value |
| <component> | <stringid> | <localstring> |
And I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Settings" in current page administration
And I press "Show more buttons"
And I press "HTML"
Then I should see "<partialtext>"
Examples:
| component | stringid | localstring | partialtext |
| core_langconfig | thisdirection | ltr | dir=\"ltr\" style=\"text-align: left;\" |
| core_langconfig | thisdirection | rtl | dir=\"rtl\" style=\"text-align: right;\" |
@@ -0,0 +1,45 @@
@editor @editor_atto @atto @editor_moodleform
Feature: Atto with enable/disable function.
In order to test enable/disable function
I create a sample page to test this feature.
As a user
I need to enable/disable all buttons/plugins and content of editor if "enable/disable" feature enabled.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| label | L1 | <a href="../lib/editor/tests/fixtures/disable_control_example.php">Control Enable/Disable Atto</a> | C1 | label1 |
And I log in as "admin"
And I am on "Course 1" course homepage
And I click on "Control Enable/Disable Atto" "link" in the "region-main" "region"
@javascript
Scenario: Check disable Atto editor.
When I set the field "mycontrol" to "Disable"
Then the "disabled" attribute of "button.atto_collapse_button" "css_element" should be set
And the "disabled" attribute of "button.atto_title_button" "css_element" should be set
And the "disabled" attribute of "button.atto_bold_button" "css_element" should be set
And the "disabled" attribute of "button.atto_italic_button" "css_element" should be set
And the "disabled" attribute of "button.atto_unorderedlist_button_insertUnorderedList" "css_element" should be set
And the "disabled" attribute of "button.atto_orderedlist_button_insertOrderedList" "css_element" should be set
And the "disabled" attribute of "button.atto_link_button" "css_element" should be set
And the "disabled" attribute of "button.atto_link_button_unlink" "css_element" should be set
And the "disabled" attribute of "button.atto_image_button" "css_element" should be set
And the "contenteditable" attribute of "div#id_myeditoreditable" "css_element" should contain "false"
@javascript
Scenario: Check enable Atto editor.
When I set the field "mycontrol" to "Enable"
Then the "disabled" attribute of "button.atto_collapse_button" "css_element" should not be set
And the "disabled" attribute of "button.atto_title_button" "css_element" should not be set
And the "disabled" attribute of "button.atto_bold_button" "css_element" should not be set
And the "disabled" attribute of "button.atto_italic_button" "css_element" should not be set
And the "disabled" attribute of "button.atto_unorderedlist_button_insertUnorderedList" "css_element" should not be set
And the "disabled" attribute of "button.atto_orderedlist_button_insertOrderedList" "css_element" should not be set
And the "disabled" attribute of "button.atto_link_button" "css_element" should not be set
And the "disabled" attribute of "button.atto_link_button_unlink" "css_element" should not be set
And the "disabled" attribute of "button.atto_image_button" "css_element" should not be set
And the "contenteditable" attribute of "div#id_myeditoreditable" "css_element" should contain "true"
@@ -0,0 +1,86 @@
<?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/>.
/**
* Demonstrates use of Atto editor with overridden toolbar setting.
*
* This fixture is only used by the Behat test.
*
* @package editor_atto
* @copyright 2016 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__ . '/../../../../../config.php');
require_once($CFG->dirroot . '/lib/editor/atto/lib.php');
// Behat test fixture only.
defined('BEHAT_SITE_RUNNING') || die('Only available on Behat test server');
$PAGE->set_url('/lib/editor/atto/tests/fixtures/override_plugins_example.php');
$PAGE->set_context(context_system::instance());
echo $OUTPUT->header();
// If this was sending some input, display it.
$normal = optional_param('normaleditor', '', PARAM_RAW);
$special = optional_param('specialeditor', '', PARAM_RAW);
if ($normal !== '' || $special !== '') {
echo html_writer::start_div('normalresult');
echo s($normal);
echo html_writer::end_div();
echo html_writer::start_div('specialresult');
echo s($special);
echo html_writer::end_div();
} else {
// Create a form.
echo html_writer::start_tag('form', array('method' => 'post', 'action' => 'custom_toolbar_example.php'));
echo html_writer::start_div();
// Basic editor options.
$options = array();
$atto = new atto_texteditor();
// Normal Atto.
echo html_writer::start_div('normaldiv');
echo $OUTPUT->heading('Normal Atto');
echo html_writer::div(html_writer::tag('textarea', '',
array('id' => 'normaleditor', 'name' => 'normaleditor', 'rows' => 10)));
$atto->use_editor('normaleditor', $options);
echo html_writer::end_div();
// Second Atto with custom options.
echo html_writer::start_div('specialdiv');
$options['atto:toolbar'] = <<<EOT
style1 = bold, italic
list = unorderedlist, orderedlist
EOT;
echo $OUTPUT->heading('Special Atto');
echo html_writer::div(html_writer::tag('textarea', '',
array('id' => 'specialeditor', 'name' => 'specialeditor', 'rows' => 10)));
$atto->use_editor('specialeditor', $options);
echo html_writer::end_div();
// Button to submit form.
echo html_writer::start_div('', array('style' => 'margin-top: 20px'));
echo html_writer::tag('button', 'Submit and see the HTML');
echo html_writer::end_div();
echo html_writer::end_div();
echo html_writer::end_tag('form');
}
echo $OUTPUT->footer();
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
WEBVTT
1
00:00:00.530 --> 00:00:00.620
Hey!
2
00:00:00.620 --> 00:00:00.710
Heey!
3
00:00:00.710 --> 00:00:00.800
Heeey!
4
00:00:00.800 --> 00:00:00.890
Heeeey!
5
00:00:00.890 --> 00:00:00.980
Heeeeey!
6
00:00:00.980 --> 00:00:01.070
Heeeeeey!
7
00:00:01.070 --> 00:00:01.160
Heeeeeeey!
8
00:00:01.160 --> 00:00:01.250
Heeeeeeeey!
9
00:00:01.250 --> 00:00:01.340
Heeeeeeeey!
1
00:00:01.340 --> 00:00:01.430
Heeeeeeeeey!
1
00:00:01.430 --> 00:00:01.510
That's
1
00:00:01.510 --> 00:00:01.770
Pretty
1
00:00:01.770 --> 00:00:03.970
Good!
+54
View File
@@ -0,0 +1,54 @@
WEBVTT
1
00:00:00.530 --> 00:00:00.620
Hej!
2
00:00:00.620 --> 00:00:00.710
Heej!
3
00:00:00.710 --> 00:00:00.800
Heeej!
4
00:00:00.800 --> 00:00:00.890
Heeeej!
5
00:00:00.890 --> 00:00:00.980
Heeeeej!
6
00:00:00.980 --> 00:00:01.070
Heeeeeej!
7
00:00:01.070 --> 00:00:01.160
Heeeeeeej!
8
00:00:01.160 --> 00:00:01.250
Heeeeeeeej!
9
00:00:01.250 --> 00:00:01.340
Heeeeeeeej!
1
00:00:01.340 --> 00:00:01.430
Heeeeeeeeej!
1
00:00:01.430 --> 00:00:01.510
Det är
1
00:00:01.510 --> 00:00:01.770
Ganska
1
00:00:01.770 --> 00:00:03.970
Bra!
@@ -0,0 +1,655 @@
<?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/>.
/**
* Unit tests for the editor_atto implementation of the privacy API.
*
* @package editor_atto
* @category test
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace editor_atto\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\writer;
use core_privacy\local\request\approved_contextlist;
use editor_atto\privacy\provider;
use core_privacy\local\request\approved_userlist;
/**
* Unit tests for the editor_atto implementation of the privacy API.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* One test to check fetch and export of all drafts.
*/
public function test_fetch_and_exports_drafts(): void {
global $USER;
$this->resetAfterTest();
// Create editor drafts in:
// - the system; and
// - a course; and
// - current user context; and
// - another user.
$systemcontext = \context_system::instance();
$course = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course->id);
$usercontextids = [];
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$usercontext = \context_user::instance($user->id);
$usercontextids[] = $usercontext->id;
$usercontextids[] = $systemcontext->id;
$usercontextids[] = $coursecontext->id;
// Add a fake inline image to the original post.
$userdraftintro = $this->create_editor_draft($usercontext, $user->id,
'id_user_intro', 'text for test user at own context');
$userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
'id_user_description', 'text for test user at own context');
$systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
'id_system_intro', 'text for test user at system context', 2);
$systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
'id_system_description', 'text for test user at system context', 4);
$coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
'id_course_intro', 'text for test user at course context');
$coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
'id_course_description', 'text for test user at course context');
// Create some data as the other user too.
$otherusercontextids = [];
$otheruser = $this->getDataGenerator()->create_user();
$this->setUser($otheruser);
$otherusercontext = \context_user::instance($otheruser->id);
$otherusercontextids[] = $otherusercontext->id;
$otherusercontextids[] = $systemcontext->id;
$otherusercontextids[] = $coursecontext->id;
$otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_intro', 'text for other user at own context');
$otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_description', 'text for other user at own context');
$systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_intro', 'text for other user at system context');
$systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_description', 'text for other user at system context');
$courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_intro', 'text for other user at course context');
$courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_description', 'text for other user at course context');
// Test as the original user.
// Get all context data for the original user.
$this->setUser($user);
$contextlist = provider::get_contexts_for_userid($user->id);
// There are three contexts in the list.
$this->assertCount(3, $contextlist);
// Check the list against the expected list of contexts.
foreach ($contextlist as $context) {
$this->assertContains($context->id, $usercontextids);
}
// Export the data for the system context.
// There should be two.
$this->export_context_data_for_user($user->id, $systemcontext, 'editor_atto');
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = \core_privacy\local\request\writer::with_context($systemcontext);
$this->assertTrue($writer->has_any_data());
$subcontextbase = [get_string('autosaves', 'editor_atto')];
// There should be an intro and description.
$intro = $writer->get_data(array_merge($subcontextbase, [$systemuserdraftintro->id]));
$fs = get_file_storage();
$this->assertEquals(
format_text($systemuserdraftintro->drafttext, FORMAT_HTML, provider::get_filter_options()),
$intro->drafttext
);
$this->assertCount(2, $writer->get_files(array_merge($subcontextbase, [$systemuserdraftintro->id])));
$description = $writer->get_data(array_merge($subcontextbase, [$systemuserdraftdescription->id]));
$this->assertEquals(
format_text($systemuserdraftdescription->drafttext, FORMAT_HTML, provider::get_filter_options()),
$description->drafttext
);
$this->assertCount(4, $writer->get_files(array_merge($subcontextbase, [$systemuserdraftdescription->id])));
}
/**
* Test delete_for_all_users_in_context.
*/
public function test_delete_for_all_users_in_context(): void {
global $USER, $DB;
$this->resetAfterTest();
// Create editor drafts in:
// - the system; and
// - a course; and
// - current user context; and
// - another user.
$systemcontext = \context_system::instance();
$course = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course->id);
$usercontextids = [];
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$usercontext = \context_user::instance($user->id);
$usercontextids[] = $usercontext->id;
$usercontextids[] = $systemcontext->id;
$usercontextids[] = $coursecontext->id;
// Add a fake inline image to the original post.
$userdraftintro = $this->create_editor_draft($usercontext, $user->id,
'id_user_intro', 'text for test user at own context');
$userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
'id_user_description', 'text for test user at own context');
$systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
'id_system_intro', 'text for test user at system context', 2);
$systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
'id_system_description', 'text for test user at system context', 4);
$coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
'id_course_intro', 'text for test user at course context');
$coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
'id_course_description', 'text for test user at course context');
// Create some data as the other user too.
$otherusercontextids = [];
$otheruser = $this->getDataGenerator()->create_user();
$this->setUser($otheruser);
$otherusercontext = \context_user::instance($otheruser->id);
$otherusercontextids[] = $otherusercontext->id;
$otherusercontextids[] = $systemcontext->id;
$otherusercontextids[] = $coursecontext->id;
$otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_intro', 'text for other user at own context');
$otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_description', 'text for other user at own context');
$systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_intro', 'text for other user at system context');
$systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_description', 'text for other user at system context');
$courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_intro', 'text for other user at course context');
$courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_description', 'text for other user at course context');
// Test deletion of the user context.
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
provider::delete_data_for_all_users_in_context($usercontext);
$this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
// No other contexts should be removed.
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
$this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
$this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
// Test deletion of the course contexts.
provider::delete_data_for_all_users_in_context($coursecontext);
$this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
$this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
// Test deletion of the system contexts.
provider::delete_data_for_all_users_in_context($systemcontext);
$this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
}
/**
* Test delete_for_all_users_in_context.
*/
public function test_delete_for_user_in_contexts(): void {
global $USER, $DB;
$this->resetAfterTest();
// Create editor drafts in:
// - the system; and
// - a course; and
// - current user context; and
// - another user.
$systemcontext = \context_system::instance();
$course = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course->id);
$usercontextids = [];
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$usercontext = \context_user::instance($user->id);
$usercontextids[] = $usercontext->id;
$usercontextids[] = $systemcontext->id;
$usercontextids[] = $coursecontext->id;
// Add a fake inline image to the original post.
$userdraftintro = $this->create_editor_draft($usercontext, $user->id,
'id_user_intro', 'text for test user at own context');
$userdraftdescription = $this->create_editor_draft($usercontext, $user->id,
'id_user_description', 'text for test user at own context');
$systemuserdraftintro = $this->create_editor_draft($systemcontext, $user->id,
'id_system_intro', 'text for test user at system context', 2);
$systemuserdraftdescription = $this->create_editor_draft($systemcontext, $user->id,
'id_system_description', 'text for test user at system context', 4);
$coursedraftintro = $this->create_editor_draft($coursecontext, $user->id,
'id_course_intro', 'text for test user at course context');
$coursedraftdescription = $this->create_editor_draft($coursecontext, $user->id,
'id_course_description', 'text for test user at course context');
// Create some data as the other user too.
$otherusercontextids = [];
$otheruser = $this->getDataGenerator()->create_user();
$this->setUser($otheruser);
$otherusercontext = \context_user::instance($otheruser->id);
$otherusercontextids[] = $otherusercontext->id;
$otherusercontextids[] = $systemcontext->id;
$otherusercontextids[] = $coursecontext->id;
$otheruserdraftintro = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_intro', 'text for other user at own context');
$otheruserdraftdescription = $this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_description', 'text for other user at own context');
$systemotheruserdraftintro = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_intro', 'text for other user at system context');
$systemotheruserdraftdescription = $this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_description', 'text for other user at system context');
$courseotheruserdraftintro = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_intro', 'text for other user at course context');
$courseotheruserdraftdescription = $this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_description', 'text for other user at course context');
// Test deletion of all data for user in usercontext only.
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'editor_atto',
[$usercontext->id]
);
provider::delete_data_for_user($contextlist);
$this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
// No other contexts should be removed.
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
$this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
$this->assertCount(4, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
// Test deletion of all data for user in course and system.
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'editor_atto',
[$coursecontext->id, $systemcontext->id]
);
provider::delete_data_for_user($contextlist);
$this->assertCount(0, $DB->get_records('editor_atto_autosave', ['contextid' => $usercontext->id]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $otherusercontext->id]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $systemcontext->id]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', ['contextid' => $coursecontext->id]));
// Data for the other user should remain.
$this->assertCount(2, $DB->get_records('editor_atto_autosave', [
'contextid' => $coursecontext->id,
'userid' => $otheruser->id,
]));
$this->assertCount(2, $DB->get_records('editor_atto_autosave', [
'contextid' => $systemcontext->id,
'userid' => $otheruser->id,
]));
}
/**
* Test that user data with different contexts is fetched.
*/
public function test_get_users_in_context(): void {
$this->resetAfterTest();
$component = 'editor_atto';
// Create editor drafts in:
// - the system; and
// - a course; and
// - current user context; and
// - another user.
$systemcontext = \context_system::instance();
// Create a course.
$course = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course->id);
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
// Add a fake inline image to the original post.
$this->create_editor_draft($usercontext, $user->id,
'id_user_intro', 'text for test user at own context');
$this->create_editor_draft($systemcontext, $user->id,
'id_system_intro', 'text for test user at system context', 2);
$this->create_editor_draft($systemcontext, $user->id,
'id_system_description', 'text for test user at system context', 4);
$this->create_editor_draft($coursecontext, $user->id,
'id_course_intro', 'text for test user at course context');
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
$this->setUser($user2);
$this->create_editor_draft($coursecontext, $user2->id,
'id_course_description', 'text for test user2 at course context');
// The list of users in usercontext should return user.
$userlist = new \core_privacy\local\request\userlist($usercontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$this->assertTrue(in_array($user->id, $userlist->get_userids()));
// The list of users in systemcontext should return user.
$userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$this->assertTrue(in_array($user->id, $userlist->get_userids()));
// The list of users in coursecontext should return user and user2.
$userlist = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(2, $userlist);
$this->assertTrue(in_array($user->id, $userlist->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist->get_userids()));
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
$this->resetAfterTest();
$component = 'editor_atto';
// Create editor drafts in:
// - the system; and
// - a course; and
// - current user context; and
// - another user.
$systemcontext = \context_system::instance();
// Create a course.
$course = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course->id);
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
// Add a fake inline image to the original post.
$this->create_editor_draft($usercontext, $user->id,
'id_user_intro', 'text for test user at own context');
$this->create_editor_draft($usercontext, $user->id,
'id_user_description', 'text for test user at own context');
$this->create_editor_draft($systemcontext, $user->id,
'id_system_intro', 'text for test user at system context', 2);
$this->create_editor_draft($systemcontext, $user->id,
'id_system_description', 'text for test user at system context', 4);
$this->create_editor_draft($coursecontext, $user->id,
'id_course_intro', 'text for test user at course context');
$this->create_editor_draft($coursecontext, $user->id,
'id_course_description', 'text for test user at course context');
// Create some data as the other user too.
$otheruser = $this->getDataGenerator()->create_user();
$otherusercontext = \context_user::instance($otheruser->id);
$this->setUser($otheruser);
$this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_intro', 'text for other user at own context');
$this->create_editor_draft($otherusercontext, $otheruser->id,
'id_user_description', 'text for other user at own context');
$this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_intro', 'text for other user at system context');
$this->create_editor_draft($systemcontext, $otheruser->id,
'id_system_description', 'text for other user at system context');
$this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_intro', 'text for other user at course context');
$this->create_editor_draft($coursecontext, $otheruser->id,
'id_course_description', 'text for other user at course context');
// The list of users for usercontext should return user.
$userlist1 = new \core_privacy\local\request\userlist($usercontext, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$this->assertTrue(in_array($user->id, $userlist1->get_userids()));
// The list of users for otherusercontext should return otheruser.
$userlist2 = new \core_privacy\local\request\userlist($otherusercontext, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$this->assertTrue(in_array($otheruser->id, $userlist2->get_userids()));
// Add userlist1 to the approved user list.
$approvedlist = new approved_userlist($usercontext, $component, $userlist1->get_userids());
// Delete user data using delete_data_for_user for usercontext.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in usercontext - The user list should now be empty.
$userlist1 = new \core_privacy\local\request\userlist($usercontext, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
// Re-fetch users in otherusercontext - The user list should not be empty (otheruser).
$userlist2 = new \core_privacy\local\request\userlist($otherusercontext, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$this->assertTrue(in_array($otheruser->id, $userlist2->get_userids()));
// The list of users for systemcontext should return user and otheruser.
$userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(2, $userlist3);
$this->assertTrue(in_array($user->id, $userlist3->get_userids()));
$this->assertTrue(in_array($otheruser->id, $userlist3->get_userids()));
// Add $userlist3 to the approved user list in the system context.
$approvedlist = new approved_userlist($systemcontext, $component, $userlist3->get_userids());
// Delete user and otheruser data using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in systemcontext - The user list should be empty.
$userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
// The list of users for coursecontext should return user and otheruser.
$userlist4 = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(2, $userlist4);
$this->assertTrue(in_array($user->id, $userlist4->get_userids()));
$this->assertTrue(in_array($otheruser->id, $userlist4->get_userids()));
// Add user to the approved user list in the course context.
$approvedlist = new approved_userlist($coursecontext, $component, [$user->id]);
// Delete user data using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in coursecontext - The user list should return otheruser.
$userlist4 = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(1, $userlist4);
$this->assertTrue(in_array($otheruser->id, $userlist4->get_userids()));
}
/**
* Test fetch and delete when another user has editted a draft in your
* user context. Edge case.
*/
public function test_another_user_edits_you(): void {
global $USER, $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$otheruser = $this->getDataGenerator()->create_user();
$otherusercontext = \context_user::instance($otheruser->id);
$this->setUser($user);
$userdraftintro = $this->create_editor_draft($usercontext, $otheruser->id,
'id_user_intro', 'text for test user at other context');
// Test as the owning user.
$this->setUser($user);
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
$firstcontext = reset($contexts);
$this->assertEquals($usercontext, $firstcontext);
// Should have the data.
$this->export_context_data_for_user($user->id, $usercontext, 'editor_atto');
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = \core_privacy\local\request\writer::with_context($usercontext);
$this->assertTrue($writer->has_any_data());
$subcontext = [
get_string('autosaves', 'editor_atto'),
$userdraftintro->id,
];
$data = $writer->get_data($subcontext);
$this->assertEquals(\core_privacy\local\request\transform::user($otheruser->id), $data->author);
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'editor_atto',
[$usercontext->id]
);
// Deleting for this context should _not_ delete as the user does not own this draft (crazy edge case, remember).
provider::delete_data_for_user($contextlist);
$records = $DB->get_records('editor_atto_autosave');
$this->assertNotEmpty($records);
$this->assertCount(1, $records);
$firstrecord = reset($records);
$this->assertEquals($userdraftintro->id, $firstrecord->id);
}
/**
* Test fetch and delete when you have edited another user's context.
*/
public function test_another_you_edit_different_user(): void {
global $USER, $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$otheruser = $this->getDataGenerator()->create_user();
$otherusercontext = \context_user::instance($otheruser->id);
$this->setUser($user);
$userdraftintro = $this->create_editor_draft($otherusercontext, $user->id,
'id_user_intro', 'text for other user you just edited.');
// Test as the context owner.
$this->setUser($user);
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
$firstcontext = reset($contexts);
$this->assertEquals($otherusercontext, $firstcontext);
// Should have the data.
$this->export_context_data_for_user($user->id, $otherusercontext, 'editor_atto');
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = \core_privacy\local\request\writer::with_context($otherusercontext);
$this->assertTrue($writer->has_any_data());
$subcontext = [
get_string('autosaves', 'editor_atto'),
$userdraftintro->id,
];
$data = $writer->get_data($subcontext);
$this->assertFalse(isset($data->author));
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'editor_atto',
[$otherusercontext->id]
);
provider::delete_data_for_user($contextlist);
$this->assertEmpty($DB->get_records('editor_atto_autosave'));
}
/**
* Create an editor draft.
*
* @param \context $context The context to create the draft for.
* @param int $userid The ID to create the draft for.
* @param string $elementid The elementid for the editor.
* @param string $text The text to write.
* @param int $filecount The number of files to create.
* @return \stdClass The editor draft.
*/
protected function create_editor_draft(\context $context, $userid, $elementid, $text, $filecount = 0) {
global $DB;
$draftid = file_get_unused_draft_itemid();
$fs = get_file_storage();
for ($i = 0; $i < $filecount; $i++) {
$fs->create_file_from_string([
'contextid' => $context->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftid,
'filepath' => '/',
'filename' => "example_{$i}.txt",
],
"Awesome example of a text file with id {$i} for {$context->id} and {$elementid}");
}
$id = $DB->insert_record('editor_atto_autosave', (object) [
'elementid' => $elementid,
'contextid' => $context->id,
'userid' => $userid,
'drafttext' => $text,
'draftid' => $draftid,
'pageinstance' => 'example_page_instance_' . rand(1, 1000),
'timemodified' => time(),
// Page hash doesn't matter for our purposes.
'pagehash' => sha1("{$userid}/{$context->id}/{$elementid}/{$draftid}"),
]);
return $DB->get_record('editor_atto_autosave', ['id' => $id]);
}
}