mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 12:32:08 +02:00
Merge branch 'MDL-67587-master-1' of git://github.com/mihailges/moodle
Changed PARAM_TEXT to PARAM_NOTAGS to "search" param because it's the same but WITHOUT lang support and we don't need lang support there. Of course, both require to verify that the output is always escaped. In this case (mustache) it is. Or also p() or s(). Without that XSS on form values are relatively easy!
This commit is contained in:
commit
594b4b98b7
@ -163,14 +163,44 @@ class content_item_service {
|
||||
* @return array the array of exported content items.
|
||||
*/
|
||||
public function get_all_content_items(\stdClass $user): array {
|
||||
global $PAGE;
|
||||
$allcontentitems = $this->repository->find_all();
|
||||
|
||||
return $this->export_content_items($user, $allcontentitems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content items which name matches a certain pattern and may be added to courses,
|
||||
* irrespective of course caps, for site admin views, etc.
|
||||
*
|
||||
* @param \stdClass $user The user object.
|
||||
* @param string $pattern The search pattern.
|
||||
* @return array The array of exported content items.
|
||||
*/
|
||||
public function get_content_items_by_name_pattern(\stdClass $user, string $pattern): array {
|
||||
$allcontentitems = $this->repository->find_all();
|
||||
|
||||
$filteredcontentitems = array_filter($allcontentitems, function($contentitem) use ($pattern) {
|
||||
return preg_match("/$pattern/i", $contentitem->get_title()->get_value());
|
||||
});
|
||||
|
||||
return $this->export_content_items($user, $filteredcontentitems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export content items.
|
||||
*
|
||||
* @param \stdClass $user The user object.
|
||||
* @param array $contentitems The content items array.
|
||||
* @return array The array of exported content items.
|
||||
*/
|
||||
private function export_content_items(\stdClass $user, $contentitems) {
|
||||
global $PAGE;
|
||||
|
||||
// Export the objects to get the formatted objects for transfer/display.
|
||||
$favourites = $this->get_favourite_content_items_for_user($user);
|
||||
$recommendations = $this->get_recommendations();
|
||||
$ciexporter = new course_content_items_exporter(
|
||||
$allcontentitems,
|
||||
$contentitems,
|
||||
[
|
||||
'context' => \context_system::instance(),
|
||||
'favouriteitems' => $favourites,
|
||||
|
@ -36,13 +36,18 @@ class activity_list implements \renderable, \templatable {
|
||||
/** @var array $modules activities to display in the recommendations page. */
|
||||
protected $modules;
|
||||
|
||||
/** @var string $searchquery The search query. */
|
||||
protected $searchquery;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*
|
||||
* @param array $modules Activities to display
|
||||
* @param string $searchquery The search query if present
|
||||
*/
|
||||
public function __construct(array $modules) {
|
||||
public function __construct(array $modules, string $searchquery) {
|
||||
$this->modules = $modules;
|
||||
$this->searchquery = $searchquery;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,6 +68,18 @@ class activity_list implements \renderable, \templatable {
|
||||
];
|
||||
}, $this->modules);
|
||||
|
||||
return ['categories' => ['categoryname' => get_string('activities'), 'categorydata' => $info]];
|
||||
return [
|
||||
'categories' => [
|
||||
[
|
||||
'categoryname' => get_string('activities'),
|
||||
'hascategorydata' => !empty($info),
|
||||
'categorydata' => $info
|
||||
]
|
||||
],
|
||||
'search' => [
|
||||
'query' => $this->searchquery,
|
||||
'searchresultsnumber' => count($this->modules)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
require_once("../config.php");
|
||||
|
||||
$search = optional_param('search', '', PARAM_TEXT);
|
||||
|
||||
$context = context_system::instance();
|
||||
$url = new moodle_url('/course/recommendations.php');
|
||||
|
||||
@ -45,9 +47,13 @@ echo $renderer->header();
|
||||
echo $renderer->heading(get_string('activitychooserrecommendations', 'course'));
|
||||
|
||||
$manager = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
$modules = $manager->get_all_content_items($USER);
|
||||
if (!empty($search)) {
|
||||
$modules = $manager->get_content_items_by_name_pattern($USER, $search);
|
||||
} else {
|
||||
$modules = $manager->get_all_content_items($USER);
|
||||
}
|
||||
|
||||
$activitylist = new \core_course\output\recommendations\activity_list($modules);
|
||||
$activitylist = new \core_course\output\recommendations\activity_list($modules, $search);
|
||||
|
||||
echo $renderer->render_activity_list($activitylist);
|
||||
|
||||
|
@ -21,31 +21,56 @@
|
||||
|
||||
No example given as the js will fire and create records from the template library page.
|
||||
}}
|
||||
{{#search}}
|
||||
<form class="row">
|
||||
<div class="input-group pt-4 pb-1 col-md-6">
|
||||
<label for="search">
|
||||
<span class="sr-only">{{#str}} searchactivitiesbyname, course {{/str}}</span>
|
||||
</label>
|
||||
<input type="text" name="search" id="search" class="form-control rounded-left" autocomplete="off"
|
||||
placeholder="{{#str}}search, core {{/str}}" {{#query}} value="{{query}}" autofocus {{/query}}
|
||||
>
|
||||
<div class="input-group-append">
|
||||
<button type="submit" class="btn btn-outline-secondary rounded-right" type="button">
|
||||
<i class="icon fa fa-search fa-fw m-0" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}submitsearch, course {{/str}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{#query}}
|
||||
<div class="pt-1 pb-1">
|
||||
<span role="alert">{{#str}} searchresults, course, {{searchresultsnumber}} {{/str}}</span>
|
||||
</div>
|
||||
{{/query}}
|
||||
{{/search}}
|
||||
{{#categories}}
|
||||
<h3>{{categoryname}}</h3>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-7 c0">{{#str}}module, course{{/str}}</th>
|
||||
<th scope="col" class="col-5 c1">{{#str}}recommend, course{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#categorydata}}
|
||||
<tr class="d-flex">
|
||||
<td class="col-7 c0"><span>{{{icon}}}</span>{{name}}</td>
|
||||
{{#id}}
|
||||
<td class="col-5 c1 colselect">
|
||||
<input class="activity-recommend-checkbox" type="checkbox" aria-label="{{#str}}recommendcheckbox, course, {{name}}{{/str}}" data-area="{{componentname}}" data-id="{{id}}" {{#recommended}}checked="checked"{{/recommended}} />
|
||||
</td>
|
||||
{{/id}}
|
||||
{{^id}}
|
||||
<td class="col-5"></td>
|
||||
{{/id}}
|
||||
</tr>
|
||||
{{/categorydata}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{#hascategorydata}}
|
||||
<h3 class="pt-4">{{categoryname}}</h3>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-7 c0">{{#str}}module, course{{/str}}</th>
|
||||
<th scope="col" class="col-5 c1">{{#str}}recommend, course{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#categorydata}}
|
||||
<tr class="d-flex">
|
||||
<td class="col-7 c0"><span>{{{icon}}}</span>{{name}}</td>
|
||||
{{#id}}
|
||||
<td class="col-5 c1 colselect">
|
||||
<input class="activity-recommend-checkbox" type="checkbox" aria-label="{{#str}}recommendcheckbox, course, {{name}}{{/str}}" data-area="{{componentname}}" data-id="{{id}}" {{#recommended}}checked="checked"{{/recommended}} />
|
||||
</td>
|
||||
{{/id}}
|
||||
{{^id}}
|
||||
<td class="col-5"></td>
|
||||
{{/id}}
|
||||
</tr>
|
||||
{{/categorydata}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/hascategorydata}}
|
||||
{{/categories}}
|
||||
{{#js}}
|
||||
require([
|
||||
|
21
course/tests/behat/search_recommended_activities.feature
Normal file
21
course/tests/behat/search_recommended_activities.feature
Normal file
@ -0,0 +1,21 @@
|
||||
@core @core_course
|
||||
Feature: Search recommended activities
|
||||
As an admin I am able to search for activities in the "Recommended activities" admin setting page
|
||||
|
||||
Scenario: Search results are returned if the search query matches any activity names
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Courses > Recommended activities" in site administration
|
||||
When I set the field "search" to "assign"
|
||||
And I click on "Submit search" "button"
|
||||
Then I should see "Search results: 1"
|
||||
And "Assignment" "table_row" should exist
|
||||
And "Book" "table_row" should not exist
|
||||
|
||||
Scenario: Search results are not returned if the search query does not match with any activity names
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Courses > Recommended activities" in site administration
|
||||
When I set the field "search" to "random query"
|
||||
And I click on "Submit search" "button"
|
||||
Then I should see "Search results: 0"
|
@ -134,6 +134,32 @@ class services_content_item_service_testcase extends \advanced_testcase {
|
||||
$this->assertContains('lti', array_column($allcontentitems, 'name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that content items which title match a certain pattern can be fetched irrespective of permissions.
|
||||
*/
|
||||
public function test_get_content_items_by_name_pattern() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
// Pattern that does exist.
|
||||
$pattern1 = "assign";
|
||||
// Pattern that does not exist.
|
||||
$pattern2 = "random string";
|
||||
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$matchingcontentitems1 = $cis->get_content_items_by_name_pattern($user, $pattern1);
|
||||
$matchingcontentitems2 = $cis->get_content_items_by_name_pattern($user, $pattern2);
|
||||
|
||||
// The pattern "assign" should return 1 content item ("Assignment").
|
||||
$this->assertCount(1, $matchingcontentitems1);
|
||||
$this->assertEquals("Assignment", $matchingcontentitems1[0]->title);
|
||||
// The pattern "random string" should not return any content items.
|
||||
$this->assertEmpty($matchingcontentitems2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that a content item can be added to a user's favourites.
|
||||
*/
|
||||
|
@ -67,6 +67,9 @@ $string['privacy:metadata:completionsummary'] = 'The course contains completion
|
||||
$string['privacy:metadata:favouritessummary'] = 'The course contains information relating to the course being starred by the user.';
|
||||
$string['recommend'] = 'Recommend';
|
||||
$string['recommendcheckbox'] = 'Recommend activity: {$a}';
|
||||
$string['searchactivitiesbyname'] = 'Search for activities by name';
|
||||
$string['searchresults'] = 'Search results: {$a}';
|
||||
$string['submitsearch'] = 'Submit search';
|
||||
$string['studentsatriskincourse'] = 'Students at risk in {$a} course';
|
||||
$string['studentsatriskinfomessage'] = 'Hi {$a->userfirstname},
|
||||
<p>Students in the {$a->coursename} course have been identified as being at risk.</p>';
|
||||
|
Loading…
x
Reference in New Issue
Block a user