| 1 |
<?php |
| 2 |
/** |
| 3 |
* Update an FAQ ability. |
| 4 |
* |
| 5 |
* @package BetterDocs |
| 6 |
* @since 4.9.0 |
| 7 |
*/ |
| 8 |
|
| 9 |
namespace WPDeveloper\BetterDocs\Abilities\Faq; |
| 10 |
|
| 11 |
if ( ! defined( 'ABSPATH' ) ) { |
| 12 |
exit; // Exit if accessed directly. |
| 13 |
} |
| 14 |
|
| 15 |
use WPDeveloper\BetterDocs\Abilities\AbilityError; |
| 16 |
|
| 17 |
/** |
| 18 |
* Edit a question, its answer, its group or its status. |
| 19 |
* |
| 20 |
* Extends {@see CreateFAQ} so both take the same fields, all optional here. |
| 21 |
* |
| 22 |
* **Everything unnamed is preserved.** `FAQBuilder::update_betterdocs_faq()` |
| 23 |
* writes `sanitize_text_field( $post_title )` and `wp_kses_post( $post_content )` |
| 24 |
* from whatever it was handed, so a call carrying only a new answer would |
| 25 |
* blank the question. This reads the FAQ first and sends both fields. |
| 26 |
* |
| 27 |
* @since 4.9.0 |
| 28 |
*/ |
| 29 |
class UpdateFAQ extends CreateFAQ { |
| 30 |
|
| 31 |
/** |
| 32 |
* @since 4.9.0 |
| 33 |
*/ |
| 34 |
public function __construct() { |
| 35 |
parent::__construct(); |
| 36 |
|
| 37 |
$this->id = 'betterdocs/update-faq'; |
| 38 |
$this->label = __( 'Update FAQ', 'betterdocs' ); |
| 39 |
$this->description = __( 'Edit a BetterDocs FAQ: its question, its answer, the group it belongs to, or its status. Fields you leave out keep their current values. Moving an FAQ to another group replaces its group rather than adding one.', 'betterdocs' ); |
| 40 |
} |
| 41 |
|
| 42 |
/** |
| 43 |
* @since 4.9.0 |
| 44 |
* |
| 45 |
* @return array |
| 46 |
*/ |
| 47 |
public function get_annotations() { |
| 48 |
return [ |
| 49 |
'readonly' => false, |
| 50 |
'destructive' => false, |
| 51 |
'idempotent' => true, |
| 52 |
'priority' => 2.0, |
| 53 |
'openWorldHint' => false |
| 54 |
]; |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* @since 4.9.0 |
| 59 |
* |
| 60 |
* @return array |
| 61 |
*/ |
| 62 |
public function get_input_schema() { |
| 63 |
$schema = parent::get_input_schema(); |
| 64 |
|
| 65 |
$schema['required'] = [ 'id' ]; |
| 66 |
|
| 67 |
unset( $schema['properties']['status']['default'] ); |
| 68 |
|
| 69 |
$schema['properties'] = array_merge( |
| 70 |
[ |
| 71 |
'id' => [ |
| 72 |
'type' => 'integer', |
| 73 |
'description' => __( 'The FAQ id. Required.', 'betterdocs' ) |
| 74 |
] |
| 75 |
], |
| 76 |
$schema['properties'] |
| 77 |
); |
| 78 |
|
| 79 |
$schema['properties']['question']['description'] = __( 'A new question. Left alone when omitted.', 'betterdocs' ); |
| 80 |
$schema['properties']['answer']['description'] = __( 'A new answer, replacing the old one. Left alone when omitted.', 'betterdocs' ); |
| 81 |
$schema['properties']['group_name']['description'] = __( 'Move the FAQ to this group, by name or slug; the group is created when nothing matches. Send group_id or group_name, not both.', 'betterdocs' ); |
| 82 |
$schema['properties']['status']['description'] = __( 'A new post status. Left alone when omitted.', 'betterdocs' ); |
| 83 |
|
| 84 |
return $schema; |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* @since 4.9.0 |
| 89 |
* |
| 90 |
* @param array $input Validated input. |
| 91 |
* @return array|\WP_Error |
| 92 |
*/ |
| 93 |
public function execute( $input ) { |
| 94 |
$id = isset( $input['id'] ) ? (int) $input['id'] : 0; |
| 95 |
$current = $this->faq_item( $id ); |
| 96 |
|
| 97 |
if ( is_wp_error( $current ) ) { |
| 98 |
return $current; |
| 99 |
} |
| 100 |
|
| 101 |
$given = array_intersect_key( $input, array_flip( [ 'question', 'answer', 'group_id', 'group_name', 'status' ] ) ); |
| 102 |
|
| 103 |
if ( empty( $given ) ) { |
| 104 |
return AbilityError::invalid_input( |
| 105 |
'input', |
| 106 |
__( 'Nothing to update: send at least one of question, answer, group_id, group_name or status.', 'betterdocs' ) |
| 107 |
); |
| 108 |
} |
| 109 |
|
| 110 |
if ( isset( $input['question'] ) && '' === trim( (string) $input['question'] ) ) { |
| 111 |
return AbilityError::invalid_input( 'question', __( 'An FAQ needs a question.', 'betterdocs' ) ); |
| 112 |
} |
| 113 |
|
| 114 |
$group = $this->group_ref_input( $input, true ); |
| 115 |
|
| 116 |
if ( is_wp_error( $group ) ) { |
| 117 |
return $group; |
| 118 |
} |
| 119 |
|
| 120 |
$written = $this->write_faq( $id, $input, $current, $group ); |
| 121 |
|
| 122 |
if ( is_wp_error( $written ) ) { |
| 123 |
return $written; |
| 124 |
} |
| 125 |
|
| 126 |
if ( isset( $input['status'] ) ) { |
| 127 |
$status = $this->apply_faq_status( $id, (string) $input['status'] ); |
| 128 |
|
| 129 |
if ( is_wp_error( $status ) ) { |
| 130 |
return $status; |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
$item = $this->faq_item( $id ); |
| 135 |
|
| 136 |
if ( is_wp_error( $item ) ) { |
| 137 |
return $item; |
| 138 |
} |
| 139 |
|
| 140 |
return $this->faq_shape( $item ); |
| 141 |
} |
| 142 |
|
| 143 |
/** |
| 144 |
* Send question, answer and group to `faq/update_post`, merged over what |
| 145 |
* the FAQ holds now. |
| 146 |
* |
| 147 |
* Skipped when only `status` was sent, so switching an FAQ to draft never |
| 148 |
* rewrites its content. |
| 149 |
* |
| 150 |
* @since 4.9.0 |
| 151 |
* |
| 152 |
* @param int $id FAQ post id. |
| 153 |
* @param array $input Validated input. |
| 154 |
* @param array $current The FAQ as it is now. |
| 155 |
* @param int|null $group Resolved group id, or null when none was sent. |
| 156 |
* @return true|\WP_Error |
| 157 |
*/ |
| 158 |
protected function write_faq( $id, array $input, array $current, $group ) { |
| 159 |
$changes = array_intersect_key( $input, array_flip( [ 'question', 'answer', 'group_id', 'group_name' ] ) ); |
| 160 |
|
| 161 |
if ( empty( $changes ) ) { |
| 162 |
return true; |
| 163 |
} |
| 164 |
|
| 165 |
$question = isset( $input['question'] ) |
| 166 |
? (string) $input['question'] |
| 167 |
: $this->rendered_or_raw_field( isset( $current['title'] ) ? $current['title'] : '' ); |
| 168 |
|
| 169 |
$answer = isset( $input['answer'] ) |
| 170 |
? $this->answer_html( $input['answer'], isset( $input['answer_format'] ) ? (string) $input['answer_format'] : 'markdown' ) |
| 171 |
: $this->rendered_or_raw_field( isset( $current['content'] ) ? $current['content'] : '' ); |
| 172 |
|
| 173 |
$result = $this->dispatch( |
| 174 |
'POST', |
| 175 |
'/faq/update_post', |
| 176 |
[ |
| 177 |
'post_id' => (int) $id, |
| 178 |
// Slashed for the same reason as on create: `wp_update_post()` |
| 179 |
// unslashes, so an unslashed answer loses its backslashes. |
| 180 |
'post_title' => wp_slash( $question ), |
| 181 |
'post_content' => wp_slash( $answer ), |
| 182 |
// 0 means "leave the group alone" — the route only touches |
| 183 |
// terms when it is given an id that exists. |
| 184 |
'term_id' => null === $group ? 0 : (int) $group |
| 185 |
], |
| 186 |
self::FAQ_NS |
| 187 |
); |
| 188 |
|
| 189 |
if ( is_wp_error( $result ) ) { |
| 190 |
return $this->map_faq_error( $result, __( 'update an FAQ', 'betterdocs' ) ); |
| 191 |
} |
| 192 |
|
| 193 |
if ( empty( $result ) ) { |
| 194 |
return AbilityError::upstream( __( 'BetterDocs did not confirm the FAQ update.', 'betterdocs' ) ); |
| 195 |
} |
| 196 |
|
| 197 |
return true; |
| 198 |
} |
| 199 |
} |
| 200 |
|