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 / Docs / CreateDoc.php

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

229 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 * Create 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\AbilityBase;
16 use WPDeveloper\BetterDocs\Abilities\AbilityError;
17 use WPDeveloper\BetterDocs\Abilities\Traits\ResolvesTerms;
18 use WPDeveloper\BetterDocs\Abilities\Traits\ShapesDocs;
19 use WPDeveloper\BetterDocs\Utils\BlockBuilder;
20
21 /**
22 * Write a new doc from markdown, HTML or block markup.
23 *
24 * Markdown is the default because it is what a language model produces well;
25 * {@see BlockBuilder} turns it into real core blocks, so the doc opens in the
26 * block editor as a normal post rather than one `core/html` lump.
27 *
28 * The write goes through `wp/v2/docs`, never `wp_insert_post()`: the REST
29 * controller slashes the content for us, and `wp_insert_post()` would
30 * `wp_unslash()` the block attributes into unparseable JSON — measured, not
31 * theoretical.
32 *
33 * @since 4.9.0
34 */
35 class CreateDoc extends AbilityBase {
36
37 use ResolvesTerms;
38 use ShapesDocs;
39
40 /**
41 * Statuses an agent may write.
42 *
43 * `future` and `trash` are deliberately absent: scheduling needs a date
44 * this tool does not take, and a doc is trashed with `bd-delete-doc`.
45 *
46 * @since 4.9.0
47 */
48 const STATUSES = [ 'draft', 'publish', 'private', 'pending' ];
49
50 /**
51 * Accepted content formats.
52 *
53 * @since 4.9.0
54 */
55 const FORMATS = [ 'markdown', 'html', 'blocks' ];
56
57 /**
58 * @since 4.9.0
59 */
60 public function __construct() {
61 $this->id = 'betterdocs/create-doc';
62 $this->label = __( 'Create doc', 'betterdocs' );
63 $this->description = __( 'Create a BetterDocs doc. Content is markdown by default and is converted to WordPress blocks. Categories, tags and glossary terms may be given by id or by name — a name that does not exist yet is created. Knowledge bases must already exist. Glossary terms need the enable_glossaries setting on.', 'betterdocs' );
64 $this->capability = 'edit_docs';
65 }
66
67 /**
68 * @since 4.9.0
69 *
70 * @return array
71 */
72 public function get_annotations() {
73 return [
74 'readonly' => false,
75 'destructive' => false,
76 'idempotent' => false,
77 'priority' => 2.0,
78 'openWorldHint' => false
79 ];
80 }
81
82 /**
83 * @since 4.9.0
84 *
85 * @return array
86 */
87 public function get_input_schema() {
88 return [
89 'type' => 'object',
90 'additionalProperties' => false,
91 'required' => [ 'title' ],
92 'properties' => [
93 'title' => [
94 'type' => 'string',
95 'description' => __( 'The doc title. Required.', 'betterdocs' )
96 ],
97 'content' => [
98 'type' => 'string',
99 'description' => __( 'The body, in the format named by content_format.', 'betterdocs' )
100 ],
101 'content_format' => [
102 'type' => 'string',
103 'enum' => self::FORMATS,
104 'default' => 'markdown',
105 'description' => __( 'How to read content: markdown (default, converted to blocks), html (converted to blocks) or blocks (already serialised block markup).', 'betterdocs' )
106 ],
107 'status' => [
108 'type' => 'string',
109 'enum' => self::STATUSES,
110 'default' => 'draft',
111 'description' => __( 'Publication status. Publishing needs the publish_docs capability.', 'betterdocs' )
112 ],
113 'excerpt' => [
114 'type' => 'string',
115 'description' => __( 'Optional summary. Plain text.', 'betterdocs' )
116 ],
117 'categories' => self::term_ref_schema( __( 'Doc categories, by id or name. A name that does not exist is created.', 'betterdocs' ) ),
118 'tags' => self::term_ref_schema( __( 'Doc tags, by id or name. A name that does not exist is created.', 'betterdocs' ) ),
119 'knowledge_bases' => self::term_ref_schema( __( 'Knowledge bases, by id, slug or name. Never created here — use bd-create-knowledge-base.', 'betterdocs' ) ),
120 'glossaries' => self::term_ref_schema( __( 'Glossary terms, by id or name. A name that does not exist is created. Needs the enable_glossaries setting on.', 'betterdocs' ) ),
121 'author' => [
122 'type' => 'integer',
123 'description' => __( 'User id to credit. Defaults to the calling user; setting someone else needs edit_others_docs.', 'betterdocs' )
124 ]
125 ],
126 'default' => []
127 ];
128 }
129
130 /**
131 * @since 4.9.0
132 *
133 * @return array
134 */
135 public function get_output_schema() {
136 return [
137 'type' => 'object',
138 'properties' => self::doc_summary_schema()
139 ];
140 }
141
142 /**
143 * @since 4.9.0
144 *
145 * @param array $input Validated input.
146 * @return array|\WP_Error
147 */
148 public function execute( $input ) {
149 $params = $this->build_params( $input, true );
150
151 if ( is_wp_error( $params ) ) {
152 return $params;
153 }
154
155 $created = $this->dispatch( 'POST', '/docs', $params, 'wp/v2' );
156
157 if ( is_wp_error( $created ) ) {
158 return $this->map_rest_error( $created, __( 'create a doc', 'betterdocs' ) );
159 }
160
161 if ( ! is_array( $created ) || empty( $created['id'] ) ) {
162 return AbilityError::upstream( __( 'The doc was not created and WordPress reported no reason.', 'betterdocs' ) );
163 }
164
165 return $this->doc_summary( $created );
166 }
167
168 /**
169 * Turn validated input into `wp/v2/docs` parameters.
170 *
171 * Shared with {@see UpdateDoc}, which passes `$creating = false` so an
172 * absent field means "leave it alone" instead of "use the default".
173 *
174 * @since 4.9.0
175 *
176 * @param array $input Validated input.
177 * @param bool $creating Whether this is a create.
178 * @return array|\WP_Error
179 */
180 protected function build_params( array $input, $creating ) {
181 $params = [];
182
183 if ( isset( $input['title'] ) ) {
184 $params['title'] = (string) $input['title'];
185 }
186
187 if ( isset( $input['excerpt'] ) ) {
188 $params['excerpt'] = (string) $input['excerpt'];
189 }
190
191 if ( isset( $input['author'] ) ) {
192 $params['author'] = (int) $input['author'];
193 }
194
195 if ( $creating || isset( $input['status'] ) ) {
196 $params['status'] = isset( $input['status'] ) ? (string) $input['status'] : 'draft';
197 }
198
199 if ( isset( $input['content'] ) ) {
200 $format = isset( $input['content_format'] ) ? (string) $input['content_format'] : 'markdown';
201
202 $params['content'] = BlockBuilder::content_to_blocks( (string) $input['content'], $format );
203 }
204
205 $taxonomies = [
206 'categories' => [ 'doc_category', true ],
207 'tags' => [ 'doc_tag', true ],
208 'knowledge_bases' => [ 'knowledge_base', false ],
209 'glossaries' => [ 'glossaries', true ]
210 ];
211
212 foreach ( $taxonomies as $field => $spec ) {
213 if ( ! isset( $input[ $field ] ) ) {
214 continue;
215 }
216
217 $ids = $this->resolve_terms( (array) $input[ $field ], $spec[0], $spec[1] );
218
219 if ( is_wp_error( $ids ) ) {
220 return $ids;
221 }
222
223 $params[ $spec[0] ] = $ids;
224 }
225
226 return $params;
227 }
228 }
229