Merge branch 'MDL-66017_course_filter_rebase' of git://github.com/davosmith/moodle

This commit is contained in:
Sara Arjona
2019-10-10 08:33:07 +02:00
26 changed files with 939 additions and 32 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("block_myoverview/view_nav",["jquery","core/custom_interaction_events","block_myoverview/repository","block_myoverview/view","block_myoverview/selectors"],function(a,b,c,d,f){var g={FILTERS:"[data-region=\"filter\"]",FILTER_OPTION:"[data-filter]",DISPLAY_OPTION:"[data-display-option]"},h=function(a,b){var d=null;if("display"==a){d="block_myoverview_user_view_preference"}else if("sort"==a){d="block_myoverview_user_sort_preference"}else{d="block_myoverview_user_grouping_preference"}c.updateUserPreferences({preferences:[{type:d,value:b}]})},i=function(c){var e=c.find(g.FILTERS);b.define(e,[b.events.activate]);e.on(b.events.activate,g.FILTER_OPTION,function(b,e){var g=a(b.target);if(g.hasClass("active")){return}var i=g.attr("data-filter"),j=g.attr("data-pref");c.find(f.courseView.region).attr("data-"+i,g.attr("data-value"));h(i,j);d.init(c);e.originalEvent.preventDefault()});b.define(e,[b.events.activate]);e.on(b.events.activate,g.DISPLAY_OPTION,function(b,e){var g=a(b.target);if(g.hasClass("active")){return}var i=g.attr("data-display-option"),j=g.attr("data-pref");c.find(f.courseView.region).attr("data-display",g.attr("data-value"));h(i,j);d.reset(c);e.originalEvent.preventDefault()})};return{init:function init(b){b=a(b);i(b)}}});
define ("block_myoverview/view_nav",["jquery","core/custom_interaction_events","block_myoverview/repository","block_myoverview/view","block_myoverview/selectors"],function(a,b,c,d,f){var g={FILTERS:"[data-region=\"filter\"]",FILTER_OPTION:"[data-filter]",DISPLAY_OPTION:"[data-display-option]"},h=function(a,b){var d=null;if("display"==a){d="block_myoverview_user_view_preference"}else if("sort"==a){d="block_myoverview_user_sort_preference"}else if("customfieldvalue"==a){d="block_myoverview_user_grouping_customfieldvalue_preference"}else{d="block_myoverview_user_grouping_preference"}c.updateUserPreferences({preferences:[{type:d,value:b}]})},i=function(c){var e=c.find(g.FILTERS);b.define(e,[b.events.activate]);e.on(b.events.activate,g.FILTER_OPTION,function(b,e){var g=a(b.target);if(g.hasClass("active")){return}var i=g.attr("data-filter"),j=g.attr("data-pref"),k=g.attr("data-customfieldvalue");c.find(f.courseView.region).attr("data-"+i,g.attr("data-value"));h(i,j);if(k){c.find(f.courseView.region).attr("data-customfieldvalue",k);h("customfieldvalue",k)}d.init(c);e.originalEvent.preventDefault()});b.define(e,[b.events.activate]);e.on(b.events.activate,g.DISPLAY_OPTION,function(b,e){var g=a(b.target);if(g.hasClass("active")){return}var i=g.attr("data-display-option"),j=g.attr("data-pref");c.find(f.courseView.region).attr("data-display",g.attr("data-value"));h(i,j);d.reset(c);e.originalEvent.preventDefault()})};return{init:function init(b){b=a(b);i(b)}}});
//# sourceMappingURL=view_nav.min.js.map
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -102,6 +102,8 @@ function(
grouping: courseRegion.attr('data-grouping'),
sort: courseRegion.attr('data-sort'),
displaycategories: courseRegion.attr('data-displaycategories'),
customfieldname: courseRegion.attr('data-customfieldname'),
customfieldvalue: courseRegion.attr('data-customfieldvalue'),
};
};
@@ -126,7 +128,9 @@ function(
offset: courseOffset,
limit: limit,
classification: filters.grouping,
sort: filters.sort
sort: filters.sort,
customfieldname: filters.customfieldname,
customfieldvalue: filters.customfieldvalue
});
};
+8
View File
@@ -55,6 +55,8 @@ function(
type = 'block_myoverview_user_view_preference';
} else if (filter == 'sort') {
type = 'block_myoverview_user_sort_preference';
} else if (filter == 'customfieldvalue') {
type = 'block_myoverview_user_grouping_customfieldvalue_preference';
} else {
type = 'block_myoverview_user_grouping_preference';
}
@@ -92,10 +94,16 @@ function(
var filter = option.attr('data-filter');
var pref = option.attr('data-pref');
var customfieldvalue = option.attr('data-customfieldvalue');
root.find(Selectors.courseView.region).attr('data-' + filter, option.attr('data-value'));
updatePreferences(filter, pref);
if (customfieldvalue) {
root.find(Selectors.courseView.region).attr('data-customfieldvalue', customfieldvalue);
updatePreferences('customfieldvalue', customfieldvalue);
}
// Reset the views.
View.init(root);
+2 -1
View File
@@ -53,8 +53,9 @@ class block_myoverview extends block_base {
$sort = get_user_preferences('block_myoverview_user_sort_preference');
$view = get_user_preferences('block_myoverview_user_view_preference');
$paging = get_user_preferences('block_myoverview_user_paging_preference');
$customfieldvalue = get_user_preferences('block_myoverview_user_grouping_customfieldvalue_preference');
$renderable = new \block_myoverview\output\main($group, $sort, $view, $paging);
$renderable = new \block_myoverview\output\main($group, $sort, $view, $paging, $customfieldvalue);
$renderer = $this->page->get_renderer('block_myoverview');
$this->content = new stdClass();
+138 -21
View File
@@ -130,6 +130,27 @@ class main implements renderable, templatable {
*/
private $displaygroupinghidden;
/**
* Store a course grouping option setting.
*
* @var bool
*/
private $displaygroupingcustomfield;
/**
* Store the custom field used by customfield grouping.
*
* @var string
*/
private $customfiltergrouping;
/**
* Store the selected custom field value to group by.
*
* @var string
*/
private $customfieldvalue;
/**
* main constructor.
* Initialize the user preferences
@@ -137,10 +158,12 @@ class main implements renderable, templatable {
* @param string $grouping Grouping user preference
* @param string $sort Sort user preference
* @param string $view Display user preference
* @param int $paging
* @param string $customfieldvalue
*
* @throws \dml_exception
*/
public function __construct($grouping, $sort, $view, $paging) {
public function __construct($grouping, $sort, $view, $paging, $customfieldvalue = null) {
// Get plugin config.
$config = get_config('block_myoverview');
@@ -153,28 +176,14 @@ class main implements renderable, templatable {
// Otherwise fall back to another grouping in a reasonable order.
// This is done to prevent one-time UI glitches in the case when a user has chosen a grouping option previously which
// was then disabled by the admin in the meantime.
} else if ($config->displaygroupingall == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALL;
} else if ($config->displaygroupingallincludinghidden == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
} else if ($config->displaygroupinginprogress == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_INPROGRESS;
} else if ($config->displaygroupingfuture == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_FUTURE;
} else if ($config->displaygroupingpast == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_PAST;
} else if ($config->displaygroupingstarred == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_FAVOURITES;
} else if ($config->displaygroupinghidden == true) {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_HIDDEN;
// In this case, no grouping option is enabled and the grouping is not needed at all.
// But it's better not to leave $this->grouping unset for any unexpected case.
} else {
$this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
$this->grouping = $this->get_fallback_grouping($config);
}
unset ($groupingconfigname);
// Remember which custom field value we were using, if grouping by custom field.
$this->customfieldvalue = $customfieldvalue;
// Check and remember the given sorting.
$this->sort = $sort ? $sort : BLOCK_MYOVERVIEW_SORTING_TITLE;
@@ -207,6 +216,8 @@ class main implements renderable, templatable {
$this->displaygroupingpast = $config->displaygroupingpast;
$this->displaygroupingstarred = $config->displaygroupingstarred;
$this->displaygroupinghidden = $config->displaygroupinghidden;
$this->displaygroupingcustomfield = ($config->displaygroupingcustomfield && $config->customfiltergrouping);
$this->customfiltergrouping = $config->customfiltergrouping;
// Check and remember if the grouping selector should be shown at all or not.
// It will be shown if more than 1 grouping option is enabled.
@@ -218,7 +229,7 @@ class main implements renderable, templatable {
$this->displaygroupingstarred,
$this->displaygroupinghidden);
$displaygroupingselectorscount = count(array_filter($displaygroupingselectors));
if ($displaygroupingselectorscount > 1) {
if ($displaygroupingselectorscount > 1 || $this->displaygroupingcustomfield) {
$this->displaygroupingselector = true;
} else {
$this->displaygroupingselector = false;
@@ -226,6 +237,41 @@ class main implements renderable, templatable {
unset ($displaygroupingselectors, $displaygroupingselectorscount);
}
/**
* Determine the most sensible fallback grouping to use (in cases where the stored selection
* is no longer available).
* @param object $config
* @return string
*/
private function get_fallback_grouping($config) {
if ($config->displaygroupingall == true) {
return BLOCK_MYOVERVIEW_GROUPING_ALL;
}
if ($config->displaygroupingallincludinghidden == true) {
return BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
}
if ($config->displaygroupinginprogress == true) {
return BLOCK_MYOVERVIEW_GROUPING_INPROGRESS;
}
if ($config->displaygroupingfuture == true) {
return BLOCK_MYOVERVIEW_GROUPING_FUTURE;
}
if ($config->displaygroupingpast == true) {
return BLOCK_MYOVERVIEW_GROUPING_PAST;
}
if ($config->displaygroupingstarred == true) {
return BLOCK_MYOVERVIEW_GROUPING_FAVOURITES;
}
if ($config->displaygroupinghidden == true) {
return BLOCK_MYOVERVIEW_GROUPING_HIDDEN;
}
if ($config->displaygroupingcustomfield == true) {
return BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD;
}
// In this case, no grouping option is enabled and the grouping is not needed at all.
// But it's better not to leave $this->grouping unset for any unexpected case.
return BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
}
/**
* Set the available layouts based on the config table settings,
@@ -292,6 +338,49 @@ class main implements renderable, templatable {
}
/**
* Get the list of values to add to the grouping dropdown
* @return object[] containing name, value and active fields
*/
public function get_customfield_values_for_export() {
global $DB, $USER;
if (!$this->displaygroupingcustomfield) {
return [];
}
$fieldid = $DB->get_field('customfield_field', 'id', ['shortname' => $this->customfiltergrouping]);
if (!$fieldid) {
return [];
}
$courses = enrol_get_all_users_courses($USER->id, true);
if (!$courses) {
return [];
}
list($csql, $params) = $DB->get_in_or_equal(array_keys($courses), SQL_PARAMS_NAMED);
$select = "instanceid $csql AND fieldid = :fieldid";
$params['fieldid'] = $fieldid;
$values = $DB->get_records_select_menu('customfield_data', $select, $params, 'value',
'DISTINCT value, value AS value2');
$values = array_filter($values);
if (!$values) {
return [];
}
$field = \core_customfield\field_controller::create($fieldid);
if (!$field->supports_course_grouping()) {
return []; // The field shouldn't have been selectable in the global settings, but just skip it now.
}
$values = $field->course_grouping_format_values($values);
$customfieldactive = ($this->grouping === BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD);
$ret = [];
foreach ($values as $value => $name) {
$ret[] = (object)[
'name' => $name,
'value' => $value,
'active' => ($customfieldactive && ($this->customfieldvalue == $value)),
];
}
return $ret;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
@@ -305,6 +394,29 @@ class main implements renderable, templatable {
$nocoursesurl = $output->image_url('courses', 'block_myoverview')->out();
$customfieldvalues = $this->get_customfield_values_for_export();
$selectedcustomfield = '';
if ($this->grouping == BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD) {
foreach ($customfieldvalues as $field) {
if ($field->value == $this->customfieldvalue) {
$selectedcustomfield = $field->name;
break;
}
}
// If the selected custom field value has not been found (possibly because the field has
// been changed in the settings) find a suitable fallback.
if (!$selectedcustomfield) {
$this->grouping = $this->get_fallback_grouping(get_config('block_myoverview'));
if ($this->grouping == BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD) {
// If the fallback grouping is still customfield, then select the first field.
$firstfield = reset($customfieldvalues);
if ($firstfield) {
$selectedcustomfield = $firstfield->name;
$this->customfieldvalue = $firstfield->value;
}
}
}
}
$preferences = $this->get_preferences_as_booleans();
$availablelayouts = $this->get_formatted_available_layouts_for_export();
@@ -327,8 +439,13 @@ class main implements renderable, templatable {
'displaygroupingstarred' => $this->displaygroupingstarred,
'displaygroupinghidden' => $this->displaygroupinghidden,
'displaygroupingselector' => $this->displaygroupingselector,
'displaygroupingcustomfield' => $this->displaygroupingcustomfield && $customfieldvalues,
'customfieldname' => $this->customfiltergrouping,
'customfieldvalue' => $this->customfieldvalue,
'customfieldvalues' => $customfieldvalues,
'selectedcustomfield' => $selectedcustomfield,
];
return array_merge($defaultvariables, $preferences);
}
}
}
@@ -33,6 +33,7 @@ $string['aria:controls'] = 'Course overview controls';
$string['aria:courseactions'] = 'Actions for current course';
$string['aria:coursesummary'] = 'Course summary text:';
$string['aria:courseprogress'] = 'Course progress:';
$string['aria:customfield'] = 'Show {$a} courses';
$string['aria:displaydropdown'] = 'Display drop-down menu';
$string['aria:favourites'] = 'Show starred courses';
$string['aria:future'] = 'Show future courses';
@@ -51,6 +52,9 @@ $string['card'] = 'Card';
$string['cards'] = 'Cards';
$string['courseprogress'] = 'Course progress:';
$string['completepercent'] = '{$a}% complete';
$string['customfield'] = 'Custom field';
$string['customfiltergrouping'] = 'Field to use';
$string['customfiltergrouping_nofields'] = 'You need to add at least one course custom field in order to use this setting';
$string['displaycategories'] = 'Display categories';
$string['displaycategories_help'] = 'Display the course category on dashboard course items including cards, list items and summary items.';
$string['favourites'] = 'Starred';
@@ -61,6 +65,7 @@ $string['layouts'] = 'Available layouts';
$string['layouts_help'] = 'Course overview layouts which are available for selection by users. If none are selected, the card layout will be used.';
$string['list'] = 'List';
$string['myoverview:myaddinstance'] = 'Add a new course overview block to Dashboard';
$string['nocustomvalue'] = 'No {$a}';
$string['past'] = 'Past';
$string['pluginname'] = 'Course overview';
$string['privacy:metadata:overviewsortpreference'] = 'The Course overview block sort preference.';
+15 -1
View File
@@ -34,6 +34,12 @@ define('BLOCK_MYOVERVIEW_GROUPING_FUTURE', 'future');
define('BLOCK_MYOVERVIEW_GROUPING_PAST', 'past');
define('BLOCK_MYOVERVIEW_GROUPING_FAVOURITES', 'favourites');
define('BLOCK_MYOVERVIEW_GROUPING_HIDDEN', 'hidden');
define('BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD', 'customfield');
/**
* Allows selection of all courses without a value for the custom field.
*/
define('BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY', -1);
/**
* Constants for the user preferences sorting options
@@ -81,9 +87,17 @@ function block_myoverview_user_preferences() {
BLOCK_MYOVERVIEW_GROUPING_FUTURE,
BLOCK_MYOVERVIEW_GROUPING_PAST,
BLOCK_MYOVERVIEW_GROUPING_FAVOURITES,
BLOCK_MYOVERVIEW_GROUPING_HIDDEN
BLOCK_MYOVERVIEW_GROUPING_HIDDEN,
BLOCK_MYOVERVIEW_GROUPING_CUSTOMFIELD,
)
);
$preferences['block_myoverview_user_grouping_customfieldvalue_preference'] = [
'null' => NULL_ALLOWED,
'default' => null,
'type' => PARAM_RAW,
];
$preferences['block_myoverview_user_sort_preference'] = array(
'null' => NULL_NOT_ALLOWED,
'default' => BLOCK_MYOVERVIEW_SORTING_TITLE,
+23
View File
@@ -86,6 +86,29 @@ if ($ADMIN->fulltree) {
'',
1));
$settings->add(new admin_setting_configcheckbox(
'block_myoverview/displaygroupingcustomfield',
get_string('customfield', 'block_myoverview'),
'',
0));
$choices = \core_customfield\api::get_fields_supporting_course_grouping();
if ($choices) {
$choices = ['' => get_string('choosedots')] + $choices;
$settings->add(new admin_setting_configselect(
'block_myoverview/customfiltergrouping',
get_string('customfiltergrouping', 'block_myoverview'),
'',
'',
$choices));
} else {
$settings->add(new admin_setting_configempty(
'block_myoverview/customfiltergrouping',
get_string('customfiltergrouping', 'block_myoverview'),
get_string('customfiltergrouping_nofields', 'block_myoverview')));
}
$settings->hide_if('block_myoverview/customfiltergrouping', 'block_myoverview/displaygroupingcustomfield');
$settings->add(new admin_setting_configcheckbox(
'block_myoverview/displaygroupingstarred',
get_string('favourites', 'block_myoverview'),
@@ -31,6 +31,8 @@
data-region="courses-view"
data-display="{{view}}"
data-grouping="{{grouping}}"
data-customfieldname="{{customfieldname}}"
data-customfieldvalue="{{customfieldvalue}}"
data-sort="{{sort}}"
data-prev-display="{{view}}"
data-paging="{{paging}}"
@@ -40,4 +42,4 @@
<div data-region="course-view-content">
{{> block_myoverview/placeholders }}
</div>
</div>
</div>
@@ -50,6 +50,7 @@
{{#past}}{{#str}} past, block_myoverview {{/str}}{{/past}}
{{#favourites}}{{#str}} favourites, block_myoverview {{/str}}{{/favourites}}
{{#hidden}}{{#str}} hiddencourses, block_myoverview {{/str}}{{/hidden}}
{{selectedcustomfield}}
</span>
</button>
<ul class="dropdown-menu" data-show-active-item data-active-item-text aria-labelledby="groupingdropdown">
@@ -106,6 +107,21 @@
</a>
</li>
{{/displaygroupingpast}}
{{#displaygroupingcustomfield}}
<li class="dropdown-divider" role="presentation">
<span class="filler">&nbsp;</span>
</li>
{{#customfieldvalues}}
<li>
<a class="dropdown-item {{#active}}active{{/active}}" href="#" data-filter="grouping"
data-value="customfield" data-pref="customfield" data-customfieldvalue="{{value}}"
aria-label="{{#str}}aria:customfield, block_myoverview, {{name}}{{/str}}"
aria-controls="courses-view-{{uniqid}}">
{{name}}
</a>
</li>
{{/customfieldvalues}}
{{/displaygroupingcustomfield}}
{{#displaygroupingstarred}}
<li class="dropdown-divider" role="presentation">
<span class="filler">&nbsp;</span>
@@ -0,0 +1,174 @@
@block @block_myoverview @javascript
Feature: The my overview block allows users to group courses by custom fields
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | X | student1@example.com | S1 |
And the following "custom field categories" exist:
| name | component | area | itemid |
| Course fields | core_course | course | 0 |
And the following "custom fields" exist:
| name | category | type | shortname | configdata |
| Checkbox field | Course fields | checkbox | checkboxfield | |
| Date field | Course fields | date | datefield | {"mindate":0, "maxdate":0} |
| Select field | Course fields | select | selectfield | {"options":"Option 1\nOption 2\nOption 3\nOption 4"} |
| Text field | Course fields | text | textfield | |
And the following "courses" exist:
| fullname | shortname | category | customfield_checkboxfield | customfield_datefield | customfield_selectfield | customfield_textfield |
| Course 1 | C1 | 0 | 1 | 981028800 | 1 | fish |
| Course 2 | C2 | 0 | 0 | 334324800 | | |
| Course 3 | C3 | 0 | 0 | 981028800 | 2 | dog |
| Course 4 | C4 | 0 | 1 | | 3 | cat |
| Course 5 | C5 | 0 | | 334411200 | 2 | fish |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
Scenario: Group courses by checkbox: Yes
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | checkboxfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "Checkbox field: Yes" "link" in the "Course overview" "block"
Then I should see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by checkbox: No
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | checkboxfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "Checkbox field: No" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should see "Course 2" in the "Course overview" "block"
And I should see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should see "Course 5" in the "Course overview" "block"
Scenario: Group courses by date: 1 February 2001
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | datefield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "1 February 2001" "link" in the "Course overview" "block"
Then I should see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by date: 6 August 1980
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | datefield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "6 August 1980" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should see "Course 5" in the "Course overview" "block"
Scenario: Group courses by date: No Date field
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | datefield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "No Date field" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by select: Option 1
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | selectfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
And I should not see "Option 4" in the "Course overview" "block"
When I click on "Option 1" "link" in the "Course overview" "block"
Then I should see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by select: Option 2
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | selectfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "Option 2" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should see "Course 5" in the "Course overview" "block"
Scenario: Group courses by select: No Select field
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | selectfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "No Select field" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by text: fish
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | textfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "fish" "link" in the "Course overview" "block"
Then I should see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should see "Course 5" in the "Course overview" "block"
Scenario: Group courses by text: dog
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | textfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "dog" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should not see "Course 2" in the "Course overview" "block"
And I should see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
Scenario: Group courses by text: No Text field
Given the following config values are set as admin:
| displaygroupingcustomfield | 1 | block_myoverview |
| customfiltergrouping | textfield | block_myoverview |
And I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
When I click on "No Text field" "link" in the "Course overview" "block"
Then I should not see "Course 1" in the "Course overview" "block"
And I should see "Course 2" in the "Course overview" "block"
And I should not see "Course 3" in the "Course overview" "block"
And I should not see "Course 4" in the "Course overview" "block"
And I should not see "Course 5" in the "Course overview" "block"
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019091800; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2019100900; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2019051100; // Requires this Moodle version.
$plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics).
+21 -2
View File
@@ -3694,7 +3694,11 @@ class core_course_external extends external_api {
'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null)
'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
VALUE_DEFAULT, null),
'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
VALUE_DEFAULT, null),
)
);
}
@@ -3717,6 +3721,8 @@ class core_course_external extends external_api {
* @param int $limit Result set limit
* @param int $offset Offset the full course set before timeline classification is applied
* @param string $sort SQL sort string for results
* @param string $customfieldname
* @param string $customfieldvalue
* @return array list of courses and warnings
* @throws invalid_parameter_exception
*/
@@ -3724,7 +3730,9 @@ class core_course_external extends external_api {
string $classification,
int $limit = 0,
int $offset = 0,
string $sort = null
string $sort = null,
string $customfieldname = null,
string $customfieldvalue = null
) {
global $CFG, $PAGE, $USER;
require_once($CFG->dirroot . '/course/lib.php');
@@ -3735,6 +3743,7 @@ class core_course_external extends external_api {
'limit' => $limit,
'offset' => $offset,
'sort' => $sort,
'customfieldvalue' => $customfieldvalue,
)
);
@@ -3742,6 +3751,7 @@ class core_course_external extends external_api {
$limit = $params['limit'];
$offset = $params['offset'];
$sort = $params['sort'];
$customfieldvalue = $params['customfieldvalue'];
switch($classification) {
case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
@@ -3758,6 +3768,8 @@ class core_course_external extends external_api {
break;
case COURSE_TIMELINE_HIDDEN:
break;
case COURSE_CUSTOMFIELD:
break;
default:
throw new invalid_parameter_exception('Invalid classification');
}
@@ -3801,6 +3813,13 @@ class core_course_external extends external_api {
$favouritecourseids,
$limit
);
} else if ($classification == COURSE_CUSTOMFIELD) {
list($filteredcourses, $processedcount) = course_filter_courses_by_customfield(
$courses,
$customfieldname,
$customfieldvalue,
$limit
);
} else {
list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
$courses,
+98
View File
@@ -63,7 +63,10 @@ define('COURSE_TIMELINE_INPROGRESS', 'inprogress');
define('COURSE_TIMELINE_FUTURE', 'future');
define('COURSE_FAVOURITES', 'favourites');
define('COURSE_TIMELINE_HIDDEN', 'hidden');
define('COURSE_CUSTOMFIELD', 'customfield');
define('COURSE_DB_QUERY_LIMIT', 1000);
/** Searching for all courses that have no value for the specified custom field. */
define('COURSE_CUSTOMFIELD_EMPTY', -1);
function make_log_url($module, $url) {
switch ($module) {
@@ -4397,6 +4400,101 @@ function course_filter_courses_by_favourites(
return [$filteredcourses, $numberofcoursesprocessed];
}
/**
* Search the given $courses for any that have a $customfieldname value that matches the given
* $customfieldvalue, up to the specified $limit.
*
* This function will return the subset of courses that matches the value as well as the
* number of courses it had to process to build that subset.
*
* It is recommended that for larger sets of courses this function is given a Generator that loads
* the courses from the database in chunks.
*
* @param array|Traversable $courses List of courses to process
* @param string $customfieldname the shortname of the custom field to match against
* @param string $customfieldvalue the value this custom field needs to match
* @param int $limit Limit the number of results to this amount
* @return array First value is the filtered courses, second value is the number of courses processed
*/
function course_filter_courses_by_customfield(
$courses,
$customfieldname,
$customfieldvalue,
int $limit = 0
) : array {
global $DB;
if (!$courses) {
return [[], 0];
}
// Prepare the list of courses to search through.
$coursesbyid = [];
foreach ($courses as $course) {
$coursesbyid[$course->id] = $course;
}
if (!$coursesbyid) {
return [[], 0];
}
list($csql, $params) = $DB->get_in_or_equal(array_keys($coursesbyid), SQL_PARAMS_NAMED);
// Get the id of the custom field.
$sql = "
SELECT f.id
FROM {customfield_field} f
JOIN {customfield_category} cat ON cat.id = f.categoryid
WHERE f.shortname = ?
AND cat.component = 'core_course'
AND cat.area = 'course'
";
$fieldid = $DB->get_field_sql($sql, [$customfieldname]);
if (!$fieldid) {
return [[], 0];
}
// Get a list of courseids that match that custom field value.
if ($customfieldvalue == COURSE_CUSTOMFIELD_EMPTY) {
$sql = "
SELECT c.id
FROM {course} c
LEFT JOIN {customfield_data} cd ON cd.instanceid = c.id AND cd.fieldid = :fieldid
WHERE c.id $csql
AND (cd.value IS NULL OR cd.value = '' OR cd.value = '0')
";
$params['fieldid'] = $fieldid;
$matchcourseids = $DB->get_fieldset_sql($sql, $params);
} else {
$select = "fieldid = :fieldid AND value = :customfieldvalue AND instanceid $csql";
$params['fieldid'] = $fieldid;
$params['customfieldvalue'] = $customfieldvalue;
$matchcourseids = $DB->get_fieldset_select('customfield_data', 'instanceid', $select, $params);
}
// Prepare the list of courses to return.
$filteredcourses = [];
$numberofcoursesprocessed = 0;
$filtermatches = 0;
foreach ($coursesbyid as $course) {
$numberofcoursesprocessed++;
if (in_array($course->id, $matchcourseids)) {
$filteredcourses[] = $course;
$filtermatches++;
}
if ($limit && $filtermatches >= $limit) {
// We've found the number of requested courses. No need to continue searching.
break;
}
}
// Return the number of filtered courses as well as the number of courses that were searched
// in order to find the matching courses. This allows the calling code to do some kind of
// pagination.
return [$filteredcourses, $numberofcoursesprocessed];
}
/**
* Check module updates since a given time.
* This function checks for updates in the module config, file areas, completion, grades, comments and ratings.
+256
View File
@@ -4866,6 +4866,262 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals($expectedprocessedcount, $processedcount);
}
/**
* Test cases for the course_filter_courses_by_timeline_classification tests.
*/
public function get_course_filter_courses_by_customfield_test_cases() {
global $CFG;
require_once($CFG->dirroot.'/blocks/myoverview/lib.php');
$coursedata = [
[
'shortname' => 'C1',
'customfield_checkboxfield' => 1,
'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
'customfield_selectfield' => 1,
'customfield_textfield' => 'fish',
],
[
'shortname' => 'C2',
'customfield_checkboxfield' => 0,
'customfield_datefield' => strtotime('1980-08-05T13:00:00Z'),
],
[
'shortname' => 'C3',
'customfield_checkboxfield' => 0,
'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
'customfield_selectfield' => 2,
'customfield_textfield' => 'dog',
],
[
'shortname' => 'C4',
'customfield_checkboxfield' => 1,
'customfield_selectfield' => 3,
'customfield_textfield' => 'cat',
],
[
'shortname' => 'C5',
'customfield_datefield' => strtotime('1980-08-06T13:00:00Z'),
'customfield_selectfield' => 2,
'customfield_textfield' => 'fish',
],
];
return [
'empty set' => [
'coursedata' => [],
'customfield' => 'checkboxfield',
'customfieldvalue' => 1,
'limit' => 10,
'offset' => 0,
'expectedcourses' => [],
'expectedprocessedcount' => 0
],
'checkbox yes' => [
'coursedata' => $coursedata,
'customfield' => 'checkboxfield',
'customfieldvalue' => 1,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C1', 'C4'],
'expectedprocessedcount' => 5
],
'checkbox no' => [
'coursedata' => $coursedata,
'customfield' => 'checkboxfield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C2', 'C3', 'C5'],
'expectedprocessedcount' => 5
],
'date 1 Feb 2001' => [
'coursedata' => $coursedata,
'customfield' => 'datefield',
'customfieldvalue' => strtotime('2001-02-01T12:00:00Z'),
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C1', 'C3'],
'expectedprocessedcount' => 5
],
'date 6 Aug 1980' => [
'coursedata' => $coursedata,
'customfield' => 'datefield',
'customfieldvalue' => strtotime('1980-08-06T13:00:00Z'),
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C5'],
'expectedprocessedcount' => 5
],
'date no date' => [
'coursedata' => $coursedata,
'customfield' => 'datefield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C4'],
'expectedprocessedcount' => 5
],
'select Option 1' => [
'coursedata' => $coursedata,
'customfield' => 'selectfield',
'customfieldvalue' => 1,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C1'],
'expectedprocessedcount' => 5
],
'select Option 2' => [
'coursedata' => $coursedata,
'customfield' => 'selectfield',
'customfieldvalue' => 2,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C3', 'C5'],
'expectedprocessedcount' => 5
],
'select no select' => [
'coursedata' => $coursedata,
'customfield' => 'selectfield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C2'],
'expectedprocessedcount' => 5
],
'text fish' => [
'coursedata' => $coursedata,
'customfield' => 'textfield',
'customfieldvalue' => 'fish',
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C1', 'C5'],
'expectedprocessedcount' => 5
],
'text dog' => [
'coursedata' => $coursedata,
'customfield' => 'textfield',
'customfieldvalue' => 'dog',
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C3'],
'expectedprocessedcount' => 5
],
'text no text' => [
'coursedata' => $coursedata,
'customfield' => 'textfield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 10,
'offset' => 0,
'expectedcourses' => ['C2'],
'expectedprocessedcount' => 5
],
'checkbox limit no' => [
'coursedata' => $coursedata,
'customfield' => 'checkboxfield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 2,
'offset' => 0,
'expectedcourses' => ['C2', 'C3'],
'expectedprocessedcount' => 3
],
'checkbox limit offset no' => [
'coursedata' => $coursedata,
'customfield' => 'checkboxfield',
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
'limit' => 2,
'offset' => 3,
'expectedcourses' => ['C5'],
'expectedprocessedcount' => 2
],
];
}
/**
* Test the course_filter_courses_by_customfield function.
*
* @dataProvider get_course_filter_courses_by_customfield_test_cases()
* @param array $coursedata Course test data to create.
* @param string $customfield Shortname of the customfield.
* @param string $customfieldvalue the value to filter by.
* @param int $limit Maximum number of results to return.
* @param int $offset Results to skip at the start of the result set.
* @param string[] $expectedcourses Expected courses in results.
* @param int $expectedprocessedcount Expected number of course records to be processed.
*/
public function test_course_filter_courses_by_customfield(
$coursedata,
$customfield,
$customfieldvalue,
$limit,
$offset,
$expectedcourses,
$expectedprocessedcount
) {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
// Create the custom fields.
$generator->create_custom_field_category([
'name' => 'Course fields',
'component' => 'core_course',
'area' => 'course',
'itemid' => 0,
]);
$generator->create_custom_field([
'name' => 'Checkbox field',
'category' => 'Course fields',
'type' => 'checkbox',
'shortname' => 'checkboxfield',
]);
$generator->create_custom_field([
'name' => 'Date field',
'category' => 'Course fields',
'type' => 'date',
'shortname' => 'datefield',
'configdata' => '{"mindate":0, "maxdate":0}',
]);
$generator->create_custom_field([
'name' => 'Select field',
'category' => 'Course fields',
'type' => 'select',
'shortname' => 'selectfield',
'configdata' => '{"options":"Option 1\nOption 2\nOption 3\nOption 4"}',
]);
$generator->create_custom_field([
'name' => 'Text field',
'category' => 'Course fields',
'type' => 'text',
'shortname' => 'textfield',
]);
$courses = array_map(function($coursedata) use ($generator) {
return $generator->create_course($coursedata);
}, $coursedata);
$student = $generator->create_user();
foreach ($courses as $course) {
$generator->enrol_user($student->id, $course->id, 'student');
}
$this->setUser($student);
$coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
list($result, $processedcount) = course_filter_courses_by_customfield(
$coursesgenerator,
$customfield,
$customfieldvalue,
$limit
);
$actual = array_map(function($course) {
return $course->shortname;
}, $result);
$this->assertEquals($expectedcourses, $actual);
$this->assertEquals($expectedprocessedcount, $processedcount);
}
/**
* Test cases for the course_filter_courses_by_timeline_classification w/ hidden courses tests.
*/
+25
View File
@@ -412,4 +412,29 @@ class api {
$field->prepare_for_config_form($formdata);
return $formdata;
}
/**
* Get a list of the course custom fields that support course grouping in
* block_myoverview
* @return array $shortname => $name
*/
public static function get_fields_supporting_course_grouping() {
global $DB;
$sql = "
SELECT f.*
FROM {customfield_field} f
JOIN {customfield_category} cat ON cat.id = f.categoryid
WHERE cat.component = 'core_course' AND cat.area = 'course'
ORDER BY f.name
";
$ret = [];
$fields = $DB->get_records_sql($sql);
foreach ($fields as $field) {
$inst = field_controller::create(0, $field);
if ($inst->supports_course_grouping()) {
$ret[$inst->get('shortname')] = $inst->get('name');
}
}
return $ret;
}
}
+19
View File
@@ -249,4 +249,23 @@ abstract class field_controller {
$context = $this->get_handler()->get_configuration_context();
return format_string($this->get('name'), true, ['context' => $context]);
}
/**
* Does this custom field type support being used as part of the block_myoverview
* custom field grouping?
* @return bool
*/
public function supports_course_grouping(): bool {
return false;
}
/**
* If this field supports course filtering, then this function needs overriding to
* return the formatted values for this.
* @param array $values the used values that need grouping
* @return array
*/
public function course_grouping_format_values($values): array {
return [];
}
}
@@ -68,4 +68,27 @@ class field_controller extends \core_customfield\field_controller {
return $errors;
}
/**
* Does this custom field type support being used as part of the block_myoverview
* custom field grouping?
* @return bool
*/
public function supports_course_grouping(): bool {
return true;
}
/**
* If this field supports course grouping, then this function needs overriding to
* return the formatted values for this.
* @param array $values the used values that need formatting
* @return array
*/
public function course_grouping_format_values($values): array {
$name = $this->get_formatted_name();
return [
1 => $name.': '.get_string('yes'),
BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY => $name.': '.get_string('no'),
];
}
}
@@ -83,4 +83,35 @@ class field_controller extends \core_customfield\field_controller {
$mform->hideIf('configdata[mindate][hour]', 'configdata[includetime]');
$mform->hideIf('configdata[mindate][minute]', 'configdata[includetime]');
}
/**
* Does this custom field type support being used as part of the block_myoverview
* custom field grouping?
* @return bool
*/
public function supports_course_grouping(): bool {
return true;
}
/**
* If this field supports course grouping, then this function needs overriding to
* return the formatted values for this.
* @param array $values the used values that need formatting
* @return array
*/
public function course_grouping_format_values($values): array {
$format = get_string('strftimedate', 'langconfig');
$ret = [];
foreach ($values as $value) {
if ($value) {
$ret[$value] = userdate($value, $format);
}
}
if (!$ret) {
return []; // If the only dates found are 0, then do not show any options.
}
$ret[BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY] = get_string('nocustomvalue', 'block_myoverview',
$this->get_formatted_name());
return $ret;
}
}
@@ -91,4 +91,32 @@ class field_controller extends \core_customfield\field_controller {
}
return $errors;
}
/**
* Does this custom field type support being used as part of the block_myoverview
* custom field grouping?
* @return bool
*/
public function supports_course_grouping(): bool {
return true;
}
/**
* If this field supports course grouping, then this function needs overriding to
* return the formatted values for this.
* @param array $values the used values that need formatting
* @return array
*/
public function course_grouping_format_values($values): array {
$options = self::get_options_array($this);
$ret = [];
foreach ($values as $value) {
if (isset($options[$value])) {
$ret[$value] = format_string($options[$value]);
}
}
$ret[BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY] = get_string('nocustomvalue', 'block_myoverview',
$this->get_formatted_name());
return $ret;
}
}
@@ -120,4 +120,29 @@ class field_controller extends \core_customfield\field_controller {
return $errors;
}
/**
* Does this custom field type support being used as part of the block_myoverview
* custom field grouping?
* @return bool
*/
public function supports_course_grouping(): bool {
return true;
}
/**
* If this field supports course grouping, then this function needs overriding to
* return the formatted values for this.
* @param array $values the used values that need formatting
* @return array
*/
public function course_grouping_format_values($values): array {
$ret = [];
foreach ($values as $value) {
$ret[$value] = format_string($value);
}
$ret[BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY] = get_string('nocustomvalue', 'block_myoverview',
$this->get_formatted_name());
return $ret;
}
}
+5
View File
@@ -0,0 +1,5 @@
This files describes API changes in /customfield/field/* - customfield field types,
information provided here is intended especially for developers.
=== 3.8 ===
* supports_course_grouping() and course_grouping_format_values() functions added to support use of custom fields in block_myoverview
+14
View File
@@ -308,6 +308,20 @@ class behat_data_generators extends behat_base {
}
/**
* Remove any empty custom fields, to avoid errors when creating the course.
* @param array $data
* @return array
*/
protected function preprocess_course($data) {
foreach ($data as $fieldname => $value) {
if ($value === '' && strpos($fieldname, 'customfield_') === 0) {
unset($data[$fieldname]);
}
}
return $data;
}
/**
* If password is not set it uses the username.
* @param array $data