mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
MDL-73547 core_courseformat: add collapsed section update actions
Now content and course index sections have special mutations to store the collapsed preferences. This way the backend implementation is independent of the frontend one and can use caches or other kind of optimizations of necessary.
This commit is contained in:
parent
c64bef51a9
commit
3651b85fbe
2
course/format/amd/build/local/content.min.js
vendored
2
course/format/amd/build/local/content.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -156,11 +156,9 @@ export default class Component extends BaseComponent {
|
|||||||
// Update the state.
|
// Update the state.
|
||||||
const sectionId = section.getAttribute('data-id');
|
const sectionId = section.getAttribute('data-id');
|
||||||
this.reactive.dispatch(
|
this.reactive.dispatch(
|
||||||
'sectionPreferences',
|
'sectionContentCollapsed',
|
||||||
[sectionId],
|
[sectionId],
|
||||||
{
|
!isCollapsed
|
||||||
contentcollapsed: !isCollapsed,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,11 +180,9 @@ export default class Component extends BaseComponent {
|
|||||||
|
|
||||||
const course = this.reactive.get('course');
|
const course = this.reactive.get('course');
|
||||||
this.reactive.dispatch(
|
this.reactive.dispatch(
|
||||||
'sectionPreferences',
|
'sectionContentCollapsed',
|
||||||
course.sectionlist ?? [],
|
course.sectionlist ?? [],
|
||||||
{
|
!isAllCollapsed
|
||||||
contentcollapsed: !isAllCollapsed,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,14 +286,42 @@ export default class {
|
|||||||
stateManager.setReadOnly(true);
|
stateManager.setReadOnly(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Get updated user preferences and state data related to some section ids.
|
* Update the course index collapsed attribute of some sections.
|
||||||
*
|
*
|
||||||
* @param {StateManager} stateManager the current state
|
* @param {StateManager} stateManager the current state manager
|
||||||
* @param {array} sectionIds the list of section ids to update
|
* @param {array} sectionIds the affected section ids
|
||||||
* @param {Object} preferences the new preferences values
|
* @param {boolean} collapsed the new collapsed value
|
||||||
*/
|
*/
|
||||||
async sectionPreferences(stateManager, sectionIds, preferences) {
|
async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {
|
||||||
|
const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);
|
||||||
|
const course = stateManager.get('course');
|
||||||
|
await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the course content collapsed attribute of some sections.
|
||||||
|
*
|
||||||
|
* @param {StateManager} stateManager the current state manager
|
||||||
|
* @param {array} sectionIds the affected section ids
|
||||||
|
* @param {boolean} collapsed the new collapsed value
|
||||||
|
*/
|
||||||
|
async sectionContentCollapsed(stateManager, sectionIds, collapsed) {
|
||||||
|
const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);
|
||||||
|
const course = stateManager.get('course');
|
||||||
|
await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private batch update for a section preference attribute.
|
||||||
|
*
|
||||||
|
* @param {StateManager} stateManager the current state manager
|
||||||
|
* @param {string} preferenceName the preference name
|
||||||
|
* @param {array} sectionIds the affected section ids
|
||||||
|
* @param {boolean} preferenceValue the new preferenceValue value
|
||||||
|
* @return {array} the list of all sections with that preference set to true
|
||||||
|
*/
|
||||||
|
_updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {
|
||||||
stateManager.setReadOnly(false);
|
stateManager.setReadOnly(false);
|
||||||
const affectedSections = new Set();
|
const affectedSections = new Set();
|
||||||
// Check if we need to update preferences.
|
// Check if we need to update preferences.
|
||||||
@ -302,41 +330,25 @@ export default class {
|
|||||||
if (section === undefined) {
|
if (section === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newValue = preferences.contentcollapsed ?? section.contentcollapsed;
|
const newValue = preferenceValue ?? section[preferenceName];
|
||||||
if (section.contentcollapsed != newValue) {
|
if (section[preferenceName] != newValue) {
|
||||||
section.contentcollapsed = newValue;
|
section[preferenceName] = newValue;
|
||||||
affectedSections.add(section.id);
|
|
||||||
}
|
|
||||||
newValue = preferences.indexcollapsed ?? section.indexcollapsed;
|
|
||||||
if (section.indexcollapsed != newValue) {
|
|
||||||
section.indexcollapsed = newValue;
|
|
||||||
affectedSections.add(section.id);
|
affectedSections.add(section.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
stateManager.setReadOnly(true);
|
stateManager.setReadOnly(true);
|
||||||
|
if (affectedSections.size == 0) {
|
||||||
if (affectedSections.size > 0) {
|
return [];
|
||||||
// Build the preference structures.
|
|
||||||
const course = stateManager.get('course');
|
|
||||||
const state = stateManager.state;
|
|
||||||
const prefKey = `coursesectionspreferences_${course.id}`;
|
|
||||||
const preferences = {
|
|
||||||
contentcollapsed: [],
|
|
||||||
indexcollapsed: [],
|
|
||||||
};
|
|
||||||
state.section.forEach(section => {
|
|
||||||
if (section.contentcollapsed) {
|
|
||||||
preferences.contentcollapsed.push(section.id);
|
|
||||||
}
|
|
||||||
if (section.indexcollapsed) {
|
|
||||||
preferences.indexcollapsed.push(section.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const jsonString = JSON.stringify(preferences);
|
|
||||||
M.util.set_user_preference(prefKey, jsonString);
|
|
||||||
// Inform the backend of the change.
|
|
||||||
await this._callEditWebservice('topic_preferences_updated', course.id, [...affectedSections]);
|
|
||||||
}
|
}
|
||||||
|
// Get all collapsed section ids.
|
||||||
|
const collapsedSectionIds = [];
|
||||||
|
const state = stateManager.state;
|
||||||
|
state.section.forEach(section => {
|
||||||
|
if (section[preferenceName]) {
|
||||||
|
collapsedSectionIds.push(section.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return collapsedSectionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,11 +135,9 @@ export default class Component extends BaseComponent {
|
|||||||
// Update the state.
|
// Update the state.
|
||||||
const sectionId = section.getAttribute('data-id');
|
const sectionId = section.getAttribute('data-id');
|
||||||
this.reactive.dispatch(
|
this.reactive.dispatch(
|
||||||
'sectionPreferences',
|
'sectionIndexCollapsed',
|
||||||
[sectionId],
|
[sectionId],
|
||||||
{
|
!isCollapsed
|
||||||
indexcollapsed: !isCollapsed,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -550,6 +550,8 @@ abstract class base {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the format section preferences.
|
* Return the format section preferences.
|
||||||
|
*
|
||||||
|
* @return array of preferences indexed by sectionid
|
||||||
*/
|
*/
|
||||||
public function get_sections_preferences(): array {
|
public function get_sections_preferences(): array {
|
||||||
global $USER;
|
global $USER;
|
||||||
@ -564,17 +566,7 @@ abstract class base {
|
|||||||
return $coursesections;
|
return $coursesections;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate collapsed preferences.
|
$sectionpreferences = $this->get_sections_preferences_by_preference();
|
||||||
try {
|
|
||||||
$sectionpreferences = (array) json_decode(
|
|
||||||
get_user_preferences('coursesectionspreferences_' . $course->id, null, $USER->id)
|
|
||||||
);
|
|
||||||
if (empty($sectionpreferences)) {
|
|
||||||
$sectionpreferences = [];
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$sectionpreferences = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($sectionpreferences as $preference => $sectionids) {
|
foreach ($sectionpreferences as $preference => $sectionids) {
|
||||||
if (!empty($sectionids) && is_array($sectionids)) {
|
if (!empty($sectionids) && is_array($sectionids)) {
|
||||||
@ -588,10 +580,48 @@ abstract class base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$coursesectionscache->set($course->id, $result);
|
$coursesectionscache->set($course->id, $result);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the format section preferences.
|
||||||
|
*
|
||||||
|
* @return array of preferences indexed by preference name
|
||||||
|
*/
|
||||||
|
public function get_sections_preferences_by_preference(): array {
|
||||||
|
global $USER;
|
||||||
|
$course = $this->get_course();
|
||||||
|
try {
|
||||||
|
$sectionpreferences = (array) json_decode(
|
||||||
|
get_user_preferences('coursesectionspreferences_' . $course->id, null, $USER->id)
|
||||||
|
);
|
||||||
|
if (empty($sectionpreferences)) {
|
||||||
|
$sectionpreferences = [];
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$sectionpreferences = [];
|
||||||
|
}
|
||||||
|
return $sectionpreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the format section preferences.
|
||||||
|
*
|
||||||
|
* @param string $preferencename preference name
|
||||||
|
* @param int[] $sectionids affected section ids
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function set_sections_preference(string $preferencename, array $sectionids) {
|
||||||
|
global $USER;
|
||||||
|
$course = $this->get_course();
|
||||||
|
$sectionpreferences = $this->get_sections_preferences_by_preference();
|
||||||
|
$sectionpreferences[$preferencename] = $sectionids;
|
||||||
|
set_user_preference('coursesectionspreferences_' . $course->id, json_encode($sectionpreferences), $USER->id);
|
||||||
|
// Invalidate section preferences cache.
|
||||||
|
$coursesectionscache = cache::make('core', 'coursesectionspreferences');
|
||||||
|
$coursesectionscache->delete($course->id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the information about the ajax support in the given source format
|
* Returns the information about the ajax support in the given source format
|
||||||
*
|
*
|
||||||
|
@ -289,11 +289,7 @@ class stateactions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some of the topic preferences has been updated.
|
* Update the course content section collapsed value.
|
||||||
*
|
|
||||||
* Section preferences can be handled by many user actions and even some of them can affect
|
|
||||||
* only the frontend part. Format plugins can override this method to add extra logic to the
|
|
||||||
* section preferences.
|
|
||||||
*
|
*
|
||||||
* @param stateupdates $updates the affected course elements track
|
* @param stateupdates $updates the affected course elements track
|
||||||
* @param stdClass $course the course object
|
* @param stdClass $course the course object
|
||||||
@ -301,15 +297,41 @@ class stateactions {
|
|||||||
* @param int $targetsectionid not used
|
* @param int $targetsectionid not used
|
||||||
* @param int $targetcmid not used
|
* @param int $targetcmid not used
|
||||||
*/
|
*/
|
||||||
public function topic_preferences_updated(
|
public function section_content_collapsed(
|
||||||
stateupdates $updates,
|
stateupdates $updates,
|
||||||
stdClass $course,
|
stdClass $course,
|
||||||
array $ids = [],
|
array $ids = [],
|
||||||
?int $targetsectionid = null,
|
?int $targetsectionid = null,
|
||||||
?int $targetcmid = null
|
?int $targetcmid = null
|
||||||
): void {
|
): void {
|
||||||
// Format plugins may override this method to provide extra functionalities to
|
if (!empty($ids)) {
|
||||||
// section preferences.
|
$this->validate_sections($course, $ids, __FUNCTION__);
|
||||||
|
}
|
||||||
|
$format = course_get_format($course->id);
|
||||||
|
$format->set_sections_preference('contentcollapsed', $ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the course index section collapsed value.
|
||||||
|
*
|
||||||
|
* @param stateupdates $updates the affected course elements track
|
||||||
|
* @param stdClass $course the course object
|
||||||
|
* @param int[] $ids the collapsed section ids
|
||||||
|
* @param int $targetsectionid not used
|
||||||
|
* @param int $targetcmid not used
|
||||||
|
*/
|
||||||
|
public function section_index_collapsed(
|
||||||
|
stateupdates $updates,
|
||||||
|
stdClass $course,
|
||||||
|
array $ids = [],
|
||||||
|
?int $targetsectionid = null,
|
||||||
|
?int $targetcmid = null
|
||||||
|
): void {
|
||||||
|
if (!empty($ids)) {
|
||||||
|
$this->validate_sections($course, $ids, __FUNCTION__);
|
||||||
|
}
|
||||||
|
$format = course_get_format($course->id);
|
||||||
|
$format->set_sections_preference('indexcollapsed', $ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,9 +261,7 @@ class base_test extends advanced_testcase {
|
|||||||
* @covers ::get_sections_preferences
|
* @covers ::get_sections_preferences
|
||||||
*/
|
*/
|
||||||
public function test_get_sections_preferences() {
|
public function test_get_sections_preferences() {
|
||||||
|
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
|
|
||||||
$generator = $this->getDataGenerator();
|
$generator = $this->getDataGenerator();
|
||||||
$course = $generator->create_course();
|
$course = $generator->create_course();
|
||||||
$user = $generator->create_and_enrol($course, 'student');
|
$user = $generator->create_and_enrol($course, 'student');
|
||||||
@ -289,7 +287,36 @@ class base_test extends advanced_testcase {
|
|||||||
(object)['pref1' => true],
|
(object)['pref1' => true],
|
||||||
$preferences[2]
|
$preferences[2]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for the default delete format data behaviour.
|
||||||
|
*
|
||||||
|
* @covers ::set_sections_preference
|
||||||
|
*/
|
||||||
|
public function test_set_sections_preference() {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$course = $generator->create_course();
|
||||||
|
$user = $generator->create_and_enrol($course, 'student');
|
||||||
|
|
||||||
|
$format = course_get_format($course);
|
||||||
|
$this->setUser($user);
|
||||||
|
|
||||||
|
// Load data from user 1.
|
||||||
|
$format->set_sections_preference('pref1', [1, 2]);
|
||||||
|
$format->set_sections_preference('pref2', [1]);
|
||||||
|
$format->set_sections_preference('pref3', []);
|
||||||
|
|
||||||
|
$preferences = $format->get_sections_preferences();
|
||||||
|
$this->assertEquals(
|
||||||
|
(object)['pref1' => true, 'pref2' => true],
|
||||||
|
$preferences[1]
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
(object)['pref1' => true],
|
||||||
|
$preferences[2]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,7 +66,6 @@ class core_course_renderer extends plugin_renderer_base {
|
|||||||
public function __construct(moodle_page $page, $target) {
|
public function __construct(moodle_page $page, $target) {
|
||||||
$this->strings = new stdClass;
|
$this->strings = new stdClass;
|
||||||
$courseid = $page->course->id;
|
$courseid = $page->course->id;
|
||||||
user_preference_allow_ajax_update('coursesectionspreferences_' . $courseid, PARAM_RAW);
|
|
||||||
parent::__construct($page, $target);
|
parent::__construct($page, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user