Skip to content
Draft
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
52 changes: 50 additions & 2 deletions classes/abilities/class-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ class Abilities {
*/
const READ_CAPABILITY = 'edit_others_posts';

/**
* The capability required to change a setting.
*
* The same capability the interactive tasks check before writing, so an
* ability can never do what the popover would refuse.
*
* @var string
*/
const WRITE_CAPABILITY = 'manage_options';

/**
* The site-score reader.
*
Expand Down Expand Up @@ -135,6 +145,27 @@ public function register_abilities() {
]
)
);

\wp_register_ability(
self::CATEGORY . '/complete-recommendation',
$this->ability_args(
[
'label' => \__( 'Complete recommendation', 'progress-planner' ),
'description' => \__( 'Apply a Progress Planner recommendation that consists of a single site setting, such as the tagline, timezone or an SEO plugin toggle. Only a fixed list of settings can be changed this way; anything needing judgement, content or deletion is reported back with a link instead of being applied.', 'progress-planner' ),
'input_schema' => Schemas::complete_recommendation_input(),
'output_schema' => Schemas::complete_recommendation(),
'permission_callback' => [ $this, 'can_fix' ],
'execute_callback' => [ $this->recommendations, 'complete' ],
'readonly' => false,
// Most of what this applies is a settings change, but two
// recommendations trash WordPress's placeholder content. The
// annotation describes what the ability can do, not what a
// given call happens to do, so it is declared destructive and
// a client prompts before any of it runs.
'destructive' => true,
]
)
);
}

/**
Expand All @@ -148,15 +179,19 @@ public function register_abilities() {
* @return array<string, mixed>
*/
private function ability_args( array $args ) {
$readonly = $args['readonly'] ?? true;
$destructive = $args['destructive'] ?? false;
unset( $args['readonly'], $args['destructive'] );

return \array_merge(
[
'category' => self::CATEGORY,
'permission_callback' => [ $this, 'can_read' ],
'meta' => [
'show_in_rest' => true,
'annotations' => [
'readonly' => true,
'destructive' => false,
'readonly' => $readonly,
'destructive' => $destructive,
'idempotent' => true,
],
],
Expand All @@ -174,6 +209,19 @@ public function can_read() {
return \current_user_can( self::READ_CAPABILITY );
}

/**
* Whether the current user may apply a fix.
*
* There is no nonce: an authenticated agent call is not a forged
* cross-origin form post, so this capability and the fixed list of settings
* in Recommendation_Fixes are what bound the write surface.
*
* @return bool
*/
public function can_fix() {
return \current_user_can( self::WRITE_CAPABILITY );
}

/**
* Whether the ability category is already registered.
*
Expand Down
Loading
Loading