PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.4
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.4
4.9.4 4.9.3 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 All 202 releases
betterdocs / includes / Shortcodes / AIActions.php

AIActions.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.4, at includes/Shortcodes/AIActions.php

186 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Shortcodes;
4
5 use WPDeveloper\BetterDocs\Core\Shortcode;
6
7 /**
8 * [betterdocs_ai_actions] — the "Copy page" split button.
9 *
10 * The single render path for the feature, the way [betterdocs_reading_time] is
11 * for reading time: the classic single-doc templates, the Gutenberg block and
12 * the Elementor widget all reach the button through this shortcode rather than
13 * calling Core\AIActions::render() themselves.
14 *
15 * Attributes:
16 * enable bool Render at all. Default true.
17 * post_id int Which doc to act on. Defaults to the current post,
18 * which is what you want inside a doc template and
19 * useless outside one.
20 * actions string Comma-separated allow-list of action ids, e.g.
21 * "copy-page,open-chatgpt". Listed actions are forced
22 * on and every other action off. Omit to inherit the
23 * global per-action settings.
24 * button_label string Overrides the `ai_actions_button_label` setting.
25 * prompt_template string Overrides the `ai_actions_prompt_template` setting.
26 * Cannot contain `]` — WP's shortcode regex excludes it
27 * from the attribute run, so the tag is truncated there
28 * and the remainder spills into the page as body text.
29 * Set the `ai_actions_prompt_template` setting instead
30 * when the prompt needs a bracket.
31 * context string Internal. See push_context().
32 *
33 * @since 4.8.0
34 */
35 class AIActions extends Shortcode {
36 /**
37 * One-shot argument handoffs for the block and the widget.
38 *
39 * Those two callers hold a typed args array — an `enabled_actions` map plus
40 * free-text overrides — and none of it survives a round trip through a
41 * shortcode attribute string:
42 *
43 * - WP's shortcode regex excludes `]` from the attribute run, and esc_attr()
44 * does not escape it, so a `]` anywhere in `prompt_template` (long, user
45 * editable, and the place a bracket is most likely to appear) truncates the
46 * tag and dumps the rest as body text.
47 * - `enabled_actions` is tri-state — absent means "inherit the global
48 * setting", present means "force on/off" (Core\AIActions::action_enabled())
49 * — and a flat comma list can only express two of those three.
50 *
51 * So internal callers stash the array here, pass the opaque token, and the
52 * shortcode pops it. Tokens are consumed on read, so a value cannot leak into
53 * a second instance on the same page.
54 *
55 * @var array<string, array>
56 */
57 private static $contexts = [];
58
59 /**
60 * Stash a typed args array and get back a shortcode-safe token.
61 *
62 * @param array $args As accepted by Core\AIActions::render().
63 * @return string
64 */
65 public static function push_context( array $args ) {
66 $token = wp_unique_id( 'bd-ai-' );
67 self::$contexts[ $token ] = $args;
68
69 return $token;
70 }
71
72 /**
73 * Pop a stashed args array. Returns null when the token is unknown, so the
74 * caller can tell "no context" from "context that happened to be empty".
75 *
76 * @param string $token
77 * @return array|null
78 */
79 private static function pull_context( $token ) {
80 if ( ! isset( self::$contexts[ $token ] ) ) {
81 return null;
82 }
83
84 $args = self::$contexts[ $token ];
85 unset( self::$contexts[ $token ] );
86
87 return $args;
88 }
89
90 public function get_name() {
91 return 'betterdocs_ai_actions';
92 }
93
94 public function get_style_depends() {
95 return [ 'betterdocs-ai-actions' ];
96 }
97
98 public function get_script_depends() {
99 return [ 'betterdocs' ];
100 }
101
102 public function default_attributes() {
103 return [
104 'enable' => true,
105 'post_id' => 0,
106 'actions' => '',
107 'button_label' => '',
108 'prompt_template' => '',
109 'context' => ''
110 ];
111 }
112
113 public function render( $atts, $content = null ) {
114 if ( ! $this->isset( 'enable' ) ) {
115 return;
116 }
117
118 $post_id = ! empty( $this->attributes['post_id'] ) ? (int) $this->attributes['post_id'] : get_the_ID();
119
120 if ( ! $post_id ) {
121 return;
122 }
123
124 betterdocs()->ai_actions->render( $post_id, $this->render_args() );
125 }
126
127 /**
128 * Per-instance overrides for Core\AIActions::render().
129 *
130 * @return array
131 */
132 protected function render_args() {
133 $context = '' !== (string) $this->attributes['context']
134 ? self::pull_context( (string) $this->attributes['context'] )
135 : null;
136
137 if ( null !== $context ) {
138 return $context;
139 }
140
141 $args = [ 'widget_type' => 'shortcode' ];
142
143 $enabled = $this->enabled_actions();
144 if ( null !== $enabled ) {
145 $args['enabled_actions'] = $enabled;
146 }
147
148 // Blank means inherit, so an unset attribute is left out entirely rather
149 // than passed as an empty string.
150 foreach ( [ 'button_label', 'prompt_template' ] as $key ) {
151 $value = (string) $this->attributes[ $key ];
152 if ( '' !== trim( $value ) ) {
153 $args[ $key ] = $value;
154 }
155 }
156
157 return $args;
158 }
159
160 /**
161 * Turn the `actions` allow-list into the tri-state map render() expects.
162 *
163 * An author writing the shortcode by hand means "show exactly these", so a
164 * non-empty list forces every known action on or off explicitly. An omitted
165 * attribute returns null, leaving every action to its global setting.
166 *
167 * @return array<string, bool>|null
168 */
169 protected function enabled_actions() {
170 $list = trim( (string) $this->attributes['actions'] );
171
172 if ( '' === $list ) {
173 return null;
174 }
175
176 $allowed = array_filter( array_map( 'trim', explode( ',', $list ) ) );
177 $enabled = [];
178
179 foreach ( betterdocs()->ai_actions->catalog() as $action ) {
180 $enabled[ $action['id'] ] = in_array( $action['id'], $allowed, true );
181 }
182
183 return $enabled;
184 }
185 }
186