Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore case option #180

Merged
merged 3 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions classes/booking_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class booking_manager {
/** @var bool When true, confirmation emails are not sent. */
private $suppressemail = false;

/** @var bool Will ignore case when matching users */
private $caseinsensitive = false;

/**
* Constructor for the booking manager.
* @param int $f The facetoface module ID.
Expand Down Expand Up @@ -165,7 +168,6 @@ private function get_iterator(): \Generator {
* @return array An array of errors.
*/
public function validate($timenow = null): array {
global $DB;
$errors = [];
$sessioncapacitycache = [];

Expand All @@ -183,7 +185,7 @@ public function validate($timenow = null): array {
$entry->discountcode = $entry->discountcode ?? '';

// Validate and get user.
$userids = $DB->get_records('user', ['email' => $entry->email], 'id');
$userids = $this->match_users($entry->email, 'id');

// Multiple matched, ambiguous which is the real one.
if (count($userids) > 1) {
Expand Down Expand Up @@ -296,6 +298,18 @@ public function validate($timenow = null): array {
return $errors;
}

/**
* Match users for a given email, taking into account case sensitivity.
* @param string $email
* @param string $fields fields to return
* @return array of users, with specified fields
*/
private function match_users(string $email, string $fields): array {
global $DB;
$equals = $DB->sql_equal('email', ':email', !$this->caseinsensitive);
return $DB->get_records_select('user', $equals, ['email' => $email], 'id', $fields);
}

/**
* Transform notification type to internal representation.
*
Expand All @@ -321,15 +335,13 @@ private function transform_notification_type($type) {
* @throws moodle_exception
*/
public function process() {
global $DB;

if (!empty($this->validate())) {
throw new moodle_exception('error:cannotprocessbookingsvalidationerrorsexist', 'facetoface');
}

// Records should be valid at this point.
foreach ($this->get_iterator() as $entry) {
$user = $DB->get_record('user', ['email' => $entry->email]);
$user = current($this->match_users($entry->email, '*'));
$session = facetoface_get_session($entry->session);

// Get signup type.
Expand Down Expand Up @@ -403,4 +415,12 @@ public function process() {
public function suppress_email() {
$this->suppressemail = true;
}

/**
* Sets case insensitive match value
* @param bool $value
*/
public function set_case_insensitive(bool $value) {
$this->caseinsensitive = $value;
}
}
4 changes: 4 additions & 0 deletions classes/form/confirm_bookings_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function definition() {
$mform = $this->_form;
$fileid = $this->_customdata['fileid'] ?? 0;
$f = $this->_customdata['f'] ?? 0;
$caseinsensitive = $this->_customdata['caseinsensitive'] ?? true;

// Suppress email checkbox.
$mform->addElement('advcheckbox', 'suppressemail', get_string('suppressemail', 'facetoface'), '', [], [0, 1]);
Expand All @@ -57,6 +58,9 @@ public function definition() {
$mform->addElement('hidden', 'fileid', $fileid);
$mform->setType('fileid', PARAM_INT);

$mform->addElement('hidden', 'caseinsensitive', $caseinsensitive);
$mform->setType('caseinsensitive', PARAM_BOOL);

$backurl = new moodle_url('/mod/facetoface/upload.php', ['f' => $f]);
$htmlbuttons = $OUTPUT->render((new single_button(
new moodle_url('/mod/facetoface/upload.php', ['f' => $f, 'fileid' => $fileid, 'process' => 1]),
Expand Down
3 changes: 3 additions & 0 deletions classes/form/upload_bookings_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public function definition() {
$mform->addElement('static', 'csvuploadhelp', '',
nl2br(get_string('facetoface:uploadbookingsfiledesc', 'mod_facetoface')));

$mform->addElement('advcheckbox', 'caseinsensitive', get_string('caseinsensitive', 'mod_facetoface'));
$mform->setDefault('caseinsensitive', true);

// The facetoface module ID.
$mform->addElement('hidden', 'f');
$mform->setType('f', PARAM_INT);
Expand Down
1 change: 1 addition & 0 deletions lang/en/facetoface.php
Original file line number Diff line number Diff line change
Expand Up @@ -833,3 +833,4 @@
$string['waitliststatus'] = 'You have a place on the waitlist of the following session';
$string['addtoallsessions'] = 'Add users to all (upcoming) sessions';
$string['addtoallsessions_help'] = 'Use this option if you want to add users to all upcoming Face-to-Face sessions. When this uption is toggled, the selected users will be added to this session and all other future sessions in the activity.';
$string['caseinsensitive'] = 'Case insensitive';
4 changes: 1 addition & 3 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@

/**
* Returns the list of possible facetoface status.
*
* @param int $statuscode One of the MDL_F2F_STATUS* constants
* @return string $string Human readable code
* @return array $string Human readable code
*/
function facetoface_statuses() {
// This array must match the status codes above, and the values
Expand Down
129 changes: 129 additions & 0 deletions tests/upload_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ public function test_user_validation() {
'notificationtype' => '',
'discountcode' => '',
],
// Test email does not match case-wise (in the default case sensitive mode).
(object) [
'email' => strtoupper($student->email),
'session' => $session->id,
'status' => '',
'notificationtype' => '',
'discountcode' => '',
],
];

$bm->load_from_array($records);
Expand Down Expand Up @@ -274,6 +282,14 @@ public function test_user_validation() {
),
'Expecting permission check conflict due to session->facetoface + facetoface id mismatcherror.'
);
$this->assertTrue(
$this->check_row_validation_error_exists(
$errors,
6,
new lang_string('error:userdoesnotexist', 'mod_facetoface', strtoupper($student->email))
),
'Expecting user to not exist because email does not match case-wise.'
);
}

/**
Expand All @@ -296,6 +312,119 @@ private function check_row_validation_error_exists(array $errors, int $expectedr
return false;
}

/**
* Tests uploading booking fails if it matches multiple users when ignoring case.
*/
public function test_processing_booking_case_insensitive_match_multiple() {
/** @var \mod_facetoface_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_facetoface');

$course = $this->getDataGenerator()->create_course();
$facetoface = $generator->create_instance(['course' => $course->id]);

// Create two users, with emails that would be the same when made lowercase.
$this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => 'test@test.com']);
$this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => 'TEST@test.com']);

$this->setCurrentTimeStart();
$now = time();
$session = $generator->create_session([
'facetoface' => $facetoface->id,
'capacity' => '3',
'allowoverbook' => '0',
'details' => 'xyz',
'duration' => '3.0',
'normalcost' => '111',
'discountcost' => '11',
'allowcancellations' => '0',
'sessiondates' => [
['timestart' => $now + 3 * DAYSECS, 'timefinish' => $now + 2 * DAYSECS],
],
]);

$bm = new booking_manager($facetoface->id);
$record = (object) [
'email' => 'TEST@test.com',
'session' => $session->id,
'status' => 'booked',
'notificationtype' => 'ical',
'discountcode' => 'mycode',
];
$records = [$record];
$bm->set_case_insensitive(true);

$bm->load_from_array($records);

$errors = $bm->validate();
$this->assertNotEmpty($errors);

$this->assertTrue(
$this->check_row_validation_error_exists(
$errors,
1,
new lang_string('error:multipleusersmatched', 'mod_facetoface', 'TEST@test.com')
),
'Expecting to error due to matching multiple users when ignoring case.',
);
}

/**
* Tests uploading a booking where the emails should match regardless of case.
*/
public function test_processing_booking_case_insensitive() {
/** @var \mod_facetoface_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_facetoface');

$course = $this->getDataGenerator()->create_course();
$facetoface = $generator->create_instance(['course' => $course->id]);
$this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => 'test@test.com']);

$this->setCurrentTimeStart();
$now = time();
$session = $generator->create_session([
'facetoface' => $facetoface->id,
'capacity' => '3',
'allowoverbook' => '0',
'details' => 'xyz',
'duration' => '1.5', // One and half hours.
'normalcost' => '111',
'discountcost' => '11',
'allowcancellations' => '0',
'sessiondates' => [
['timestart' => $now + 3 * DAYSECS, 'timefinish' => $now + 2 * DAYSECS],
],
]);

$bm = new booking_manager($facetoface->id);
$record = (object) [
'email' => 'TEST@test.com',
'session' => $session->id,
'status' => 'booked',
'notificationtype' => 'ical',
'discountcode' => 'MYSPECIALCODE',
];
$records = [$record];
$bm->set_case_insensitive(true);

$bm->load_from_array($records);

$errors = $bm->validate();
$this->assertEmpty($errors);
$this->assertTrue($bm->process());

// Check users are as expected.
$users = facetoface_get_attendees($session->id);
$this->assertCount(1, $users);
$this->assertEquals(strtolower($record->email), strtolower(current($users)->email));
$this->assertEquals($record->discountcode, current($users)->discountcode);
$this->assertEquals(MDL_F2F_ICAL, current($users)->notificationtype);
$this->assertEquals(MDL_F2F_STATUS_BOOKED, current($users)->statuscode);

// Re-booking the same user shouldn't cause any isssues. Run the validate again and check.
$errors = $bm->validate();
$this->assertEmpty($errors);
}

/**
* Test upload processing to ensure the happy path is working as expected, and users can be booked into a session.
*/
Expand Down
5 changes: 4 additions & 1 deletion upload.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
$validate = optional_param('validate', 0, PARAM_INT); // Whether or not the user wants to process the upload (after verification).
$process = optional_param('process', 0, PARAM_INT); // Whether or not the user wants to process the upload (after verification).
$step = optional_param('step', '', PARAM_ALPHA); // The current step in the process.
$caseinsensitive = optional_param('caseinsensitive', false, PARAM_BOOL); // If emails should match a user case insensitively.

if (!$facetoface = $DB->get_record('facetoface', ['id' => $f])) {
throw new moodle_exception('error:incorrectfacetofaceid', 'facetoface');
Expand Down Expand Up @@ -63,10 +64,11 @@
$data = $mform->get_data();
$fileid = $data->csvfile ?: 0;

$mform = new confirm_bookings_form(null, ['f' => $f, 'fileid' => $fileid]);
$mform = new confirm_bookings_form(null, ['f' => $f, 'fileid' => $fileid, 'caseinsensitive' => $caseinsensitive]);

$bm = new booking_manager($f);
$bm->load_from_file($fileid);
$bm->set_case_insensitive($caseinsensitive);

// Validate entries.
$errors = $bm->validate();
Expand All @@ -76,6 +78,7 @@
// Form submitted, and ready for processing -> process.
$bm = new booking_manager($f);
$bm->load_from_file($fileid);
$bm->set_case_insensitive($caseinsensitive);

// Get the options selected by the user at confirm time.
$confirmdata = (new confirm_bookings_form(null))->get_data();
Expand Down
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2023101906;
$plugin->release = 2023101906;
$plugin->version = 2023101907;
$plugin->release = 2023101907;
$plugin->requires = 2022031500; // Requires 4.0.
$plugin->component = 'mod_facetoface';
$plugin->maturity = MATURITY_STABLE;
Expand Down
Loading