PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.1
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Abilities / Faq / UpdateFAQ.php

UpdateFAQ.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.1, at includes/Abilities/Faq/UpdateFAQ.php

200 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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