diff --git a/admin/tool/policy/classes/external/get_user_acceptances.php b/admin/tool/policy/classes/external/get_user_acceptances.php index 227e22f6754..ba3f2541689 100644 --- a/admin/tool/policy/classes/external/get_user_acceptances.php +++ b/admin/tool/policy/classes/external/get_user_acceptances.php @@ -118,6 +118,11 @@ class get_user_acceptances extends external_api { [$policy['acceptance']['note']] = util::format_text($version->acceptance->note, FORMAT_MOODLE, $systemcontext); } } + // Return permission for actions for the current policy and user. + $policy['canaccept'] = api::can_accept_policies([$version->id], $user->id); + $policy['candecline'] = api::can_decline_policies([$version->id], $user->id); + $policy['canrevoke'] = api::can_revoke_policies([$version->id], $user->id); + $policies[] = $policy; } } @@ -157,6 +162,9 @@ class get_user_acceptances extends external_api { 'note' => new external_value(PARAM_TEXT, 'The policy note/remarks.', VALUE_OPTIONAL), 'modfullname' => new external_value(PARAM_NOTAGS, 'The fullname who accepted on behalf.', VALUE_OPTIONAL), ], 'Acceptance status for the given user.', VALUE_OPTIONAL), + 'canaccept' => new external_value(PARAM_BOOL, 'Whether the policy can be accepted.'), + 'candecline' => new external_value(PARAM_BOOL, 'Whether the policy can be declined.'), + 'canrevoke' => new external_value(PARAM_BOOL, 'Whether the policy can be revoked.'), ]), 'Policies and acceptance status for the given user.', VALUE_OPTIONAL ), 'warnings' => new external_warnings(), diff --git a/admin/tool/policy/classes/external/set_acceptances_status.php b/admin/tool/policy/classes/external/set_acceptances_status.php index 0bfb6d410b8..01caae7ee20 100644 --- a/admin/tool/policy/classes/external/set_acceptances_status.php +++ b/admin/tool/policy/classes/external/set_acceptances_status.php @@ -48,11 +48,14 @@ class set_acceptances_status extends external_api { new external_single_structure([ 'versionid' => new external_value(PARAM_INT, 'The policy version id.'), 'status' => new external_value(PARAM_INT, 'The policy acceptance status. 0: decline, 1: accept.'), + 'note' => new external_value(PARAM_NOTAGS, + 'Any comments added by a user when giving consent on behalf of another user.', VALUE_OPTIONAL, null), ]), 'Policies acceptances for the given user.' ), 'userid' => new external_value(PARAM_INT, 'The user id we want to set the acceptances. Default is the current user.', VALUE_DEFAULT, 0 ), + ] ); } @@ -91,18 +94,25 @@ class set_acceptances_status extends external_api { } // Split acceptances. - $allcurrentpolicies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN); - $requestedpolicies = $agreepolicies = $declinepolicies = []; + $requestedpolicies = $agreepolicies = $declinepolicies = $notes = []; foreach ($params['policies'] as $policy) { $requestedpolicies[$policy['versionid']] = $policy['status']; + if ($USER->id != $user->id) { + // Notes are only allowed when setting acceptances on behalf of another user. + $notes[$policy['versionid']] = $policy['note'] ?? null; + } } - foreach ($allcurrentpolicies as $policy) { - if (isset($requestedpolicies[$policy->id])) { - if ($requestedpolicies[$policy->id] === 1) { - $agreepolicies[] = $policy->id; - } else if ($requestedpolicies[$policy->id] === 0) { - $declinepolicies[] = $policy->id; + // Retrieve all policies and their acceptances. + $allpolicies = api::get_policies_with_acceptances($user->id); + foreach ($allpolicies as $policy) { + foreach ($policy->versions as $version) { + if (isset($requestedpolicies[$version->id])) { + if ($requestedpolicies[$version->id] === 1) { + $agreepolicies[] = $version->id; + } else if ($requestedpolicies[$version->id] === 0) { + $declinepolicies[] = $version->id; + } } } } @@ -112,8 +122,12 @@ class set_acceptances_status extends external_api { api::can_decline_policies($declinepolicies, $user->id, true); // Good to go. - api::accept_policies($agreepolicies, $user->id, null); - api::decline_policies($declinepolicies, $user->id, null); + foreach ($agreepolicies as $policyversionid) { + api::accept_policies($policyversionid, $user->id, $notes[$policyversionid] ?? null); + } + foreach ($declinepolicies as $policyversionid) { + api::decline_policies($policyversionid, $user->id, $notes[$policyversionid] ?? null); + } $return = [ 'policyagreed' => (int) $user->policyagreed, // Final policy agreement status for $user. diff --git a/admin/tool/policy/tests/externallib_test.php b/admin/tool/policy/tests/externallib_test.php index 9796b2fae00..b7b029ab9bb 100644 --- a/admin/tool/policy/tests/externallib_test.php +++ b/admin/tool/policy/tests/externallib_test.php @@ -260,20 +260,34 @@ class externallib_test extends externallib_advanced_testcase { $formdata->content_editor = ['text' => 'content', 'format' => FORMAT_HTML, 'itemid' => 0]; $optionalpolicy = api::form_policydoc_add($formdata); api::make_current($optionalpolicy->get('id')); + // Accept this version. + api::accept_policies([$optionalpolicy->get('id')], $user->id, null); + // Generate new version. + $formdata = api::form_policydoc_data($optionalpolicy); + $formdata->revision = 'v2'; + $optionalpolicynew = api::form_policydoc_update_new($formdata); + api::make_current($optionalpolicynew->get('id')); + // Now return all policies the user should be able to see, including previous versions of existing policies, if accepted/declined. $policies = \tool_policy\external\get_user_acceptances::execute(); $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\get_user_acceptances::execute_returns(), $policies); - $this->assertCount(2, $policies['policies']); + $this->assertCount(3, $policies['policies']); $this->assertCount(0, $policies['warnings']); foreach ($policies['policies'] as $policy) { if ($policy['versionid'] == $this->policy2->get('id')) { $this->assertEquals($this->policy2->get('name'), $policy['name']); $this->assertEquals(0, $policy['optional']); + $this->assertTrue($policy['canaccept']); + $this->assertFalse($policy['candecline']); // Cannot decline or revoke mandatory for myself. + $this->assertFalse($policy['canrevoke']); } else { $this->assertEquals($optionalpolicy->get('name'), $policy['name']); $this->assertEquals(1, $policy['optional']); + $this->assertTrue($policy['canaccept']); + $this->assertTrue($policy['candecline']); // Can decline or revoke optional for myself. + $this->assertTrue($policy['canrevoke']); } $this->assertNotContains('acceptance', $policy); // Nothing accepted yet. } @@ -285,6 +299,18 @@ class externallib_test extends externallib_advanced_testcase { $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\get_user_acceptances::execute_returns(), $policies); $this->assertCount(2, $policies['policies']); + foreach ($policies['policies'] as $policy) { + if ($policy['versionid'] == $this->policy2->get('id')) { + $this->assertTrue($policy['canaccept']); + $this->assertFalse($policy['candecline']); // Cannot decline mandatory in general. + $this->assertTrue($policy['canrevoke']); + } else { + $this->assertTrue($policy['canaccept']); + $this->assertTrue($policy['candecline']); + $this->assertTrue($policy['canrevoke']); + } + $this->assertNotContains('acceptance', $policy); // Nothing accepted yet. + } // Get other user acceptances without permission. $this->expectException(\required_capability_exception::class); @@ -311,9 +337,19 @@ class externallib_test extends externallib_advanced_testcase { $formdata->content_editor = ['text' => 'content', 'format' => FORMAT_HTML, 'itemid' => 0]; $optionalpolicy = api::form_policydoc_add($formdata); api::make_current($optionalpolicy->get('id')); + // Decline this version. + api::decline_policies([$optionalpolicy->get('id')], $user->id, null); + // Generate new version and make it current. + $formdata = api::form_policydoc_data($optionalpolicy); + $formdata->revision = 'v2'; + $optionalpolicynew = api::form_policydoc_update_new($formdata); + api::make_current($optionalpolicynew->get('id')); - // Accept all the policies. - $ids = [['versionid' => $this->policy2->get('id'), 'status' => 1], ['versionid' => $optionalpolicy->get('id'), 'status' => 1]]; + // Accept all the current policies. + $ids = [ + ['versionid' => $this->policy2->get('id'), 'status' => 1], + ['versionid' => $optionalpolicynew->get('id'), 'status' => 1], + ]; $policies = \tool_policy\external\set_acceptances_status::execute($ids); $policies = \core_external\external_api::clean_returnvalue( @@ -322,17 +358,25 @@ class externallib_test extends externallib_advanced_testcase { $this->assertEquals(1, $policies['policyagreed']); $this->assertCount(0, $policies['warnings']); + // And now accept and old one. + $ids = [['versionid' => $optionalpolicy->get('id'), 'status' => 1, 'note' => 'I accept for me.']]; // The note will be ignored. + $policies = \tool_policy\external\set_acceptances_status::execute($ids); + $policies = \core_external\external_api::clean_returnvalue( + \tool_policy\external\set_acceptances_status::execute_returns(), $policies); + + // Retrieve and check all are accepted now. $policies = \tool_policy\external\get_user_acceptances::execute(); $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\get_user_acceptances::execute_returns(), $policies); - $this->assertCount(2, $policies['policies']); + $this->assertCount(3, $policies['policies']); foreach ($policies['policies'] as $policy) { $this->assertEquals(1, $policy['acceptance']['status']); // Check all accepted. + $this->assertEmpty($policy['acceptance']['note']); // The note was not recorded because it was for itself. } // Decline optional only. - $policies = \tool_policy\external\set_acceptances_status::execute([['versionid' => $optionalpolicy->get('id'), 'status' => 0]]); + $policies = \tool_policy\external\set_acceptances_status::execute([['versionid' => $optionalpolicynew->get('id'), 'status' => 0]]); $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\set_acceptances_status::execute_returns(), $policies); @@ -343,19 +387,21 @@ class externallib_test extends externallib_advanced_testcase { $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\get_user_acceptances::execute_returns(), $policies); - $this->assertCount(2, $policies['policies']); + $this->assertCount(3, $policies['policies']); foreach ($policies['policies'] as $policy) { - if ($policy['versionid'] == $this->policy2->get('id')) { - $this->assertEquals(1, $policy['acceptance']['status']); // Still accepted. - } else { + if ($policy['versionid'] == $optionalpolicynew->get('id')) { $this->assertEquals(0, $policy['acceptance']['status']); // Not accepted. + } else { + $this->assertEquals(1, $policy['acceptance']['status']); // Accepted. } } // Parent & child case now. Accept the optional ONLY on behalf of someone else. $this->parent->policyagreed = 1; $this->setUser($this->parent); - $policies = \tool_policy\external\set_acceptances_status::execute([['versionid' => $optionalpolicy->get('id'), 'status' => 1]], $this->child->id); + $notetext = 'I accept this on behalf of my child Santiago.'; + $policies = \tool_policy\external\set_acceptances_status::execute( + [['versionid' => $optionalpolicynew->get('id'), 'status' => 1, 'note' => $notetext]], $this->child->id); $policies = \core_external\external_api::clean_returnvalue( \tool_policy\external\set_acceptances_status::execute_returns(), $policies); @@ -370,14 +416,16 @@ class externallib_test extends externallib_advanced_testcase { foreach ($policies['policies'] as $policy) { if ($policy['versionid'] == $this->policy2->get('id')) { $this->assertNotContains('acceptance', $policy); // Not yet accepted. + $this->assertArrayNotHasKey('acceptance', $policy); } else { $this->assertEquals(1, $policy['acceptance']['status']); // Accepted. + $this->assertEquals($notetext, $policy['acceptance']['note']); } } // Try to accept on behalf of other user with no permissions. $this->expectException(\required_capability_exception::class); - $policies = \tool_policy\external\set_acceptances_status::execute([['versionid' => $optionalpolicy->get('id'), 'status' => 1]], $user->id); + $policies = \tool_policy\external\set_acceptances_status::execute([['versionid' => $optionalpolicynew->get('id'), 'status' => 1]], $user->id); } /**