diff --git a/classes/helper.php b/classes/helper.php index a04f09a..ff04132 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -300,6 +300,46 @@ public static function course_display_name(int $courseid): string { return $course->fullname; } + /** + * Displays the user who locked the profile and the date it was locked. + * @param profile $profile + * @return string + */ + public static function lock_display_modified($profile): string { + global $DB; + + // Only proceed if there's a lock. + if (empty($profile->get('lockreason'))) { + return ''; + } + + // Locks are the only time a profile should be modified after being fully saved. + $userid = $profile->get('usermodified'); + $modified = $profile->get('timemodified'); + + // If we have neither userid or time modified a lock doesn't exist. + if (empty($userid) && empty($modified)) { + return ''; + } + + // We don't want to show user modified if it was set before the lock (i.e. during creation). + // No exact comparison, but can check it's different from created or greater than finished plus a small buffer. + if ($modified === $profile->get('timecreated') || ($profile->get('finished') + 10) > $modified) { + return ''; + } + + $user = $DB->get_record('user', ['id' => $userid]); + if ($user) { + $link = new \moodle_url('/user/profile.php', ['id' => $userid]); + $userdisplay = \html_writer::link($link, fullname($user)); + } else { + $userdisplay = get_string('unknown', 'tool_excimer'); + } + $date = userdate($modified, get_string('strftimedate', 'core_langconfig')); + + return get_string('lockedinfo', 'tool_excimer', ['user' => $userdisplay, 'date' => $date]); + } + /** * Returns a HTML link based on the lockwait url. * @param string|null $lockwaiturl Relative lockwait url @@ -327,7 +367,7 @@ public static function lockwait_display_link(?string $lockwaiturl, float $lockwa * @return array export for template */ public static function lockwait_display_help(\renderer_base $output, string $lockwaiturl) { - GLOBAL $CFG; + global $CFG; // Only show help information if we have an 'Unknown' url and debug session lock is off. if ($lockwaiturl === get_string('unknown', 'tool_excimer') && empty($CFG->debugsessionlock)) { diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 1bc48e3..3aad08e 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -53,6 +53,9 @@ public static function get_metadata(collection $collection): collection { 'pathinfo' => 'field_pathinfo', 'parameters' => 'field_parameters', 'referer' => 'field_referer', + 'usermodified' => 'field_usermodified', + 'lockreason' => 'field_lockreason', + 'timemodified' => 'field_timemodified', ], 'privacy:metadata:tool_excimer_profiles' ); @@ -82,6 +85,7 @@ public static function export_user_data(approved_contextlist $contextlist) { $userid = $contextlist->get_user()->id; foreach ($contextlist as $context) { if ($context->contextlevel == CONTEXT_SYSTEM) { + // Profile data. $list = []; $rows = $DB->get_records(profile::TABLE, ['userid' => $userid]); foreach ($rows as $row) { @@ -94,6 +98,19 @@ public static function export_user_data(approved_contextlist $contextlist) { 'referer' => $row->referer, ]; } + // Lock data. + $rows = $DB->get_records(profile::TABLE, ['usermodified' => $userid]); + foreach ($rows as $row) { + // Ignore if we have no lock and usermodified is covered by the profile. + if (empty($row->lockreason) && $row->userid === $userid) { + continue; + } + $list[] = [ + 'usermodified' => $userid, + 'lockreason' => $row->lockreason, + 'timemodified' => $row->timemodified, + ]; + } writer::with_context($context)->export_data( [get_string('privacy:metadata:tool_excimer_profiles', 'tool_excimer')], (object) $list @@ -125,6 +142,7 @@ public static function delete_data_for_user(approved_contextlist $contextlist) { foreach ($contextlist as $context) { if ($context->contextlevel == CONTEXT_SYSTEM) { $DB->delete_records(profile::TABLE, ['userid' => $userid]); + $DB->set_field(profile::TABLE, 'usermodified', 0, ['usermodified' => $userid]); } } } @@ -139,6 +157,7 @@ public static function get_users_in_context(userlist $userlist) { if ($context->contextlevel == CONTEXT_SYSTEM) { $sql = "SELECT * FROM {tool_excimer_profiles}"; $userlist->add_from_sql('userid', $sql, []); + $userlist->add_from_sql('usermodified', $sql, []); } } @@ -154,6 +173,7 @@ public static function delete_data_for_users(approved_userlist $userlist) { $users = $userlist->get_users(); foreach ($users as $user) { $DB->delete_records(profile::TABLE, ['userid' => $user->id]); + $DB->set_field(profile::TABLE, 'usermodified', 0, ['usermodified' => $user->id]); } } } diff --git a/classes/profile.php b/classes/profile.php index 049e803..ffc0e84 100644 --- a/classes/profile.php +++ b/classes/profile.php @@ -393,6 +393,40 @@ protected function check_update_courseid(int $currentid): bool { return false; } + /** + * Updates the lock status of a profile. + * + * @param string $lockreason reason for the lock, or an empty string to remove the lock. + * @return void + */ + public function update_lock(string $lockreason) { + global $DB, $USER; + + $previousreason = $this->get('lockreason'); + $updatefields = [ + 'id' => $this->get('id'), + 'lockreason' => $lockreason, + ]; + + $now = time(); + + // Update modified info when creating a new lock. + if ($lockreason && !$previousreason) { + $updatefields['usermodified'] = $USER->id; + $updatefields['timemodified'] = $now; + } + + // Reset modified info when removing a lock. + if (!$lockreason) { + // Return usermodified to what it was originally (userid should be an exact match). + $updatefields['usermodified'] = $this->get('userid'); + $updatefields['timemodified'] = $now; + } + + // Update directly as save() won't work with memoryusagedatad3 and flamedatad3. + $DB->update_record('tool_excimer_profiles', (object) $updatefields); + } + /** * Returns the slowest profile on record. * diff --git a/lang/en/tool_excimer.php b/lang/en/tool_excimer.php index b658827..622f030 100644 --- a/lang/en/tool_excimer.php +++ b/lang/en/tool_excimer.php @@ -154,6 +154,10 @@ $string['field_hostname'] = 'Host name'; $string['field_useragent'] = 'User agent'; $string['field_versionhash'] = 'Version Hash'; +$string['field_usermodified'] = 'User modified'; +$string['field_timecreated'] = 'Time created'; +$string['field_timemodified'] = 'Time modified'; +$string['field_lockreason'] = 'Lock reason'; $string['field_courseid'] = 'Course'; $string['field_lockheld'] = 'Session held'; $string['field_lockwait'] = 'Session wait'; @@ -191,6 +195,7 @@ // Lock reason form. $string['lock_profile'] = 'Lock Profile'; $string['locked'] = 'Profile is locked'; +$string['lockedinfo'] = 'Locked by {$a->user} on {$a->date}'; $string['lockreason'] = 'Lock Profile Reason'; $string['lockreason_help'] = 'Submitting text will prevent this profile from being deleted. It will not be purged during cleanup tasks, nor can it be deleted manually (will also be excluded from group deletes). diff --git a/lock_profile.php b/lock_profile.php index 7af166a..fb69a63 100644 --- a/lock_profile.php +++ b/lock_profile.php @@ -51,7 +51,8 @@ $form = new \tool_excimer\form\lock_reason_form($url); if ($data = $form->get_data()) { - $DB->update_record('tool_excimer_profiles', (object) ['id' => $profileid, 'lockreason' => trim($data->lockreason)]); + $lockreason = trim($data->lockreason); + $profile->update_lock($lockreason); redirect($data->returnurl); } else { $form->set_data(['lockreason' => $DB->get_field('tool_excimer_profiles', 'lockreason', ['id' => $profileid])]); diff --git a/profile.php b/profile.php index 2d4f59e..10ff215 100644 --- a/profile.php +++ b/profile.php @@ -198,6 +198,7 @@ $data['fullname'] = '-'; } $data['lockreason'] = format_text($data['lockreason']); +$data['lockmodified'] = helper::lock_display_modified($profile); $tabs = new tabs($url); // JavaScript locale string. Arguably "localecldr/langconfig" would be a better diff --git a/templates/profile.mustache b/templates/profile.mustache index e5da622..dbb9085 100644 --- a/templates/profile.mustache +++ b/templates/profile.mustache @@ -63,6 +63,9 @@ {{#lockreason}}