PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 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 All 200 releases
betterdocs / includes / Abilities / Docs / UpdateDoc.php

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

206 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update a doc ability.
4 *
5 * @package BetterDocs
6 * @since 4.9.0
7 */
8
9 namespace WPDeveloper\BetterDocs\Abilities\Docs;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly.
13 }
14
15 use WPDeveloper\BetterDocs\Abilities\AbilityError;
16 use WPDeveloper\BetterDocs\Utils\BlockBuilder;
17
18 /**
19 * Change a doc that already exists.
20 *
21 * Two things are worth an agent knowing, and both are in the tool description
22 * because getting either wrong silently destroys work:
23 *
24 * - **Term arrays replace, they do not merge.** `{categories: ["How-to"]}`
25 * leaves the doc in exactly that one category. Omit the field to leave the
26 * terms alone; send the full list to change them.
27 * - **`content` replaces the whole body; `append_content` adds to it.** The
28 * append path reads the doc's *raw* content first, so existing blocks come
29 * back byte-identical and only new markup is added at the end.
30 *
31 * Extends {@see CreateDoc} for the shared parameter building, which is the
32 * only way the two can be guaranteed to accept the same fields in the same way.
33 *
34 * @since 4.9.0
35 */
36 class UpdateDoc extends CreateDoc {
37
38 /**
39 * @since 4.9.0
40 */
41 public function __construct() {
42 parent::__construct();
43
44 $this->id = 'betterdocs/update-doc';
45 $this->label = __( 'Update doc', 'betterdocs' );
46 $this->description = __( 'Update a BetterDocs doc by id. Only the fields you send change. content replaces the whole body; append_content adds to the end of it. Category, tag, knowledge base and glossary term lists REPLACE what the doc has — omit them to leave the terms alone.', 'betterdocs' );
47 $this->capability = 'edit_docs';
48 }
49
50 /**
51 * @since 4.9.0
52 *
53 * @return array
54 */
55 public function get_annotations() {
56 return [
57 'readonly' => false,
58 'destructive' => false,
59 'idempotent' => true,
60 'priority' => 2.0,
61 'openWorldHint' => false
62 ];
63 }
64
65 /**
66 * @since 4.9.0
67 *
68 * @return array
69 */
70 public function get_input_schema() {
71 $schema = parent::get_input_schema();
72
73 $schema['required'] = [ 'id' ];
74
75 $schema['properties']['id'] = [
76 'type' => 'integer',
77 'description' => __( 'The doc id to update. Required.', 'betterdocs' )
78 ];
79
80 $schema['properties']['title']['description'] = __( 'A new title. Omit to leave it alone.', 'betterdocs' );
81
82 $schema['properties']['content']['description'] = __( 'Replacement body, in the format named by content_format. Omit to leave the body alone.', 'betterdocs' );
83
84 $schema['properties']['append_content'] = [
85 'type' => 'string',
86 'description' => __( 'Content to add to the end of the doc, in the format named by content_format. The existing blocks are left byte-identical. Cannot be combined with content.', 'betterdocs' )
87 ];
88
89 $schema['properties']['status']['description'] = __( 'A new publication status. Omit to leave it alone.', 'betterdocs' );
90
91 unset( $schema['properties']['status']['default'] );
92
93 $schema['properties']['categories']['description'] = __( 'REPLACES the doc categories, by id or name. A name that does not exist is created. Omit to leave them alone; send [] to clear them.', 'betterdocs' );
94 $schema['properties']['tags']['description'] = __( 'REPLACES the doc tags, by id or name. A name that does not exist is created. Omit to leave them alone; send [] to clear them.', 'betterdocs' );
95 $schema['properties']['knowledge_bases']['description'] = __( 'REPLACES the knowledge bases, by id, slug or name. Never created here. Omit to leave them alone; send [] to clear them.', 'betterdocs' );
96 $schema['properties']['glossaries']['description'] = __( 'REPLACES the glossary terms, by id or name. A name that does not exist is created. Omit to leave them alone; send [] to clear them. Needs the enable_glossaries setting on.', 'betterdocs' );
97
98 // Put `id` first: it is the one field a reader must notice.
99 $schema['properties'] = array_merge(
100 [ 'id' => $schema['properties']['id'] ],
101 $schema['properties']
102 );
103
104 return $schema;
105 }
106
107 /**
108 * @since 4.9.0
109 *
110 * @param array $input Validated input.
111 * @return array|\WP_Error
112 */
113 public function execute( $input ) {
114 $post = $this->require_doc( isset( $input['id'] ) ? $input['id'] : 0 );
115
116 if ( is_wp_error( $post ) ) {
117 return $post;
118 }
119
120 // Asked before the REST controller does, so the refusal can name the
121 // capability this particular doc needs rather than the generic one.
122 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
123 return AbilityError::capability_missing(
124 $this->editing_capability( $post ),
125 sprintf(
126 /* translators: %d: doc id. */
127 __( 'edit doc #%d', 'betterdocs' ),
128 (int) $post->ID
129 )
130 );
131 }
132
133 if ( isset( $input['content'], $input['append_content'] ) ) {
134 return AbilityError::invalid_input(
135 'append_content',
136 __( 'Send either content (replaces the body) or append_content (adds to it), not both.', 'betterdocs' )
137 );
138 }
139
140 $params = $this->build_params( $input, false );
141
142 if ( is_wp_error( $params ) ) {
143 return $params;
144 }
145
146 if ( isset( $input['append_content'] ) ) {
147 $appended = $this->append( $post->ID, (string) $input['append_content'], isset( $input['content_format'] ) ? (string) $input['content_format'] : 'markdown' );
148
149 if ( is_wp_error( $appended ) ) {
150 return $appended;
151 }
152
153 $params['content'] = $appended;
154 }
155
156 if ( [] === $params ) {
157 return AbilityError::invalid_input(
158 'input',
159 __( 'Nothing to update: send at least one field besides id.', 'betterdocs' )
160 );
161 }
162
163 $updated = $this->dispatch( 'POST', '/docs/' . (int) $post->ID, $params, 'wp/v2' );
164
165 if ( is_wp_error( $updated ) ) {
166 return $this->map_rest_error(
167 $updated,
168 sprintf(
169 /* translators: %d: doc id. */
170 __( 'edit doc #%d', 'betterdocs' ),
171 (int) $post->ID
172 ),
173 $post
174 );
175 }
176
177 return $this->doc_summary( (array) $updated );
178 }
179
180 /**
181 * The doc's current raw content with new markup appended.
182 *
183 * Read through `context=edit` so what comes back is the stored block markup
184 * and not the rendered HTML — appending to rendered output would rewrite
185 * every existing block.
186 *
187 * @since 4.9.0
188 *
189 * @param int $id Doc id.
190 * @param string $content Content to append.
191 * @param string $format Its format.
192 * @return string|\WP_Error
193 */
194 protected function append( $id, $content, $format ) {
195 $current = $this->dispatch( 'GET', '/docs/' . (int) $id, [ 'context' => 'edit' ], 'wp/v2' );
196
197 if ( is_wp_error( $current ) ) {
198 return $this->map_rest_error( $current, __( 'read the doc before appending to it', 'betterdocs' ) );
199 }
200
201 $raw = isset( $current['content']['raw'] ) ? (string) $current['content']['raw'] : '';
202
203 return BlockBuilder::append_block( $raw, BlockBuilder::content_to_blocks( $content, $format ) );
204 }
205 }
206