PluginProbe
ZIP AI – AI Website Builder & AI Agent (Beta) / 0.0.5
ZIP AI – AI Website Builder & AI Agent (Beta) v0.0.5
0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / classes / admin / snippet-admin.php

snippet-admin.php in ZIP AI – AI Website Builder & AI Agent (Beta) 0.0.5, at classes/admin/snippet-admin.php

255 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Snippet Admin - Mounts the React-based snippets management SPA.
4 *
5 * @since 0.1.0
6 * @package zip-ai
7 */
8
9 namespace ZipAI\MCP\Classes\Admin;
10
11 use ZipAI\MCP\Classes\Core\Snippet_Store;
12 use ZipAI\MCP\Classes\Core\Snippet_Executor;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 class Snippet_Admin {
19
20 private $hook_suffix = '';
21
22 public function __construct() {
23 if ( ! is_admin() ) {
24 return;
25 }
26
27 add_action( 'admin_menu', array( $this, 'register_submenu' ) );
28 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
29 }
30
31 public function register_submenu() {
32 if ( ! current_user_can( 'manage_options' ) ) {
33 return;
34 }
35
36 $this->hook_suffix = add_submenu_page(
37 'options-general.php',
38 __( 'ZIP AI Code Snippets', 'zip-ai' ),
39 __( 'ZIP AI Code Snippets', 'zip-ai' ),
40 'manage_options',
41 'zip-ai-snippets',
42 array( $this, 'render_page' )
43 );
44 }
45
46 public function enqueue_assets( $hook_suffix ) {
47 if ( $this->hook_suffix !== $hook_suffix ) {
48 return;
49 }
50
51 // React SPA bundle.
52 $asset_path = ZIPAI_MCP_DIR . 'assets/js/dist/snippets-page.asset.php';
53 $asset = file_exists( $asset_path ) ? include $asset_path : array();
54
55 wp_enqueue_script(
56 'zip-ai-snippets-page',
57 ZIPAI_MCP_URL . 'assets/js/dist/snippets-page.js',
58 $asset['dependencies'] ?? array( 'react', 'react-dom' ),
59 $asset['version'] ?? ZIPAI_MCP_VERSION,
60 true
61 );
62
63 // Single source of truth: enqueue the shared Tailwind/ZIP AI stylesheet built
64 // from src/styles/main.css. Tokens + utilities are scoped to both
65 // #chat-assistant-root and #zip-ai-snippets-root via tailwind.config.js.
66 wp_enqueue_style(
67 'zip-ai-snippets-page',
68 ZIPAI_MCP_URL . 'assets/css/dist/chat-assistant.css',
69 array(),
70 $asset['version'] ?? ZIPAI_MCP_VERSION
71 );
72
73 // Mount point sits flush against the WP admin canvas. The SPA owns its
74 // own layout/padding via Tailwind — the `.wrap` chrome adds margins
75 // that fight the design, so we zero the margins WP injects on
76 // `#wpbody-content`'s default flow.
77 wp_add_inline_style(
78 'zip-ai-snippets-page',
79 '#wpbody-content { padding: 0; } #wpcontent { padding-left: 0; padding-top: 0; } #wpbody { padding-top: 0 !important; } #wpfooter { display: none !important; } #zip-ai-snippets-root-wrap { margin: 0; padding: 0; }'
80 );
81
82 // CodeMirror 6 editor.
83 $editor_asset_path = ZIPAI_MCP_DIR . 'assets/js/dist/snippet-editor.asset.php';
84 $editor_asset = file_exists( $editor_asset_path ) ? include $editor_asset_path : array();
85
86 wp_enqueue_script(
87 'zip-ai-snippet-editor',
88 ZIPAI_MCP_URL . 'assets/js/dist/snippet-editor.js',
89 $editor_asset['dependencies'] ?? array(),
90 $editor_asset['version'] ?? ZIPAI_MCP_VERSION,
91 true
92 );
93
94 // Config for the React app.
95 wp_localize_script(
96 'zip-ai-snippets-page',
97 'eraSnippetConfig',
98 array(
99 'restUrl' => rest_url( 'zip-ai/v1/snippets' ),
100 'nonce' => wp_create_nonce( 'wp_rest' ),
101 'hooks' => array(
102 'php' => Snippet_Store::VALID_PHP_HOOKS,
103 'js' => Snippet_Store::VALID_JS_HOOKS,
104 'css' => Snippet_Store::VALID_CSS_HOOKS,
105 'html' => Snippet_Store::VALID_HTML_HOOKS,
106 ),
107 'scopes' => Snippet_Store::VALID_SCOPES,
108 'conditionTypes' => array(
109 array(
110 'value' => 'page',
111 'label' => __( 'Page Type', 'zip-ai' ),
112 ),
113 array(
114 'value' => 'user',
115 'label' => __( 'User State', 'zip-ai' ),
116 ),
117 array(
118 'value' => 'user_role',
119 'label' => __( 'User Role', 'zip-ai' ),
120 ),
121 array(
122 'value' => 'post_type',
123 'label' => __( 'Post Type', 'zip-ai' ),
124 ),
125 array(
126 'value' => 'post',
127 'label' => __( 'Specific Post / Page', 'zip-ai' ),
128 ),
129 array(
130 'value' => 'device',
131 'label' => __( 'Device', 'zip-ai' ),
132 ),
133 array(
134 'value' => 'url_pattern',
135 'label' => __( 'URL Pattern', 'zip-ai' ),
136 ),
137 ),
138 'conditionValues' => array(
139 'page' => array(
140 array(
141 'value' => 'home',
142 'label' => __( 'Homepage', 'zip-ai' ),
143 ),
144 array(
145 'value' => 'single',
146 'label' => __( 'Single Post', 'zip-ai' ),
147 ),
148 array(
149 'value' => 'page',
150 'label' => __( 'Page', 'zip-ai' ),
151 ),
152 array(
153 'value' => 'archive',
154 'label' => __( 'Archive', 'zip-ai' ),
155 ),
156 array(
157 'value' => '404',
158 'label' => __( '404 Page', 'zip-ai' ),
159 ),
160 array(
161 'value' => 'search',
162 'label' => __( 'Search Results', 'zip-ai' ),
163 ),
164 ),
165 'user' => array(
166 array(
167 'value' => 'logged_in',
168 'label' => __( 'Logged In', 'zip-ai' ),
169 ),
170 ),
171 'user_role' => self::get_user_roles(),
172 'post_type' => self::get_post_types(),
173 'device' => array(
174 array(
175 'value' => 'mobile',
176 'label' => __( 'Mobile', 'zip-ai' ),
177 ),
178 ),
179 // post + url_pattern are open-ended (no fixed list); UI uses
180 // autocomplete (post) or free-text input (url_pattern).
181 'post' => array(),
182 'url_pattern' => array(),
183 ),
184 'conditionOperators' => array(
185 'page' => array( 'is', 'is_not' ),
186 'user' => array( 'is', 'is_not' ),
187 'user_role' => array( 'is', 'is_not', 'in', 'not_in' ),
188 'post_type' => array( 'is', 'is_not', 'in', 'not_in' ),
189 'post' => array( 'is', 'is_not', 'in', 'not_in' ),
190 'device' => array( 'is', 'is_not' ),
191 'url_pattern' => array( 'is', 'is_not', 'contains', 'starts_with', 'ends_with', 'regex' ),
192 ),
193 'conditionMulti' => array(
194 // Types where the UI should render a chip multi-picker.
195 'user_role' => true,
196 'post_type' => true,
197 'post' => true,
198 ),
199 'safeModeActive' => Snippet_Executor::is_persistent_safe_mode(),
200 )
201 );
202 }
203
204 /**
205 * Render the React mount point.
206 */
207 public function render_page() {
208 // Canvas-reset CSS is enqueued via wp_add_inline_style() in enqueue_assets().
209 echo '<div id="zip-ai-snippets-root-wrap"><div id="zip-ai-snippets-root"></div></div>';
210 }
211
212 /**
213 * Get all user roles as value/label pairs.
214 *
215 * @since 0.0.5
216 * @return array
217 */
218 private static function get_user_roles() {
219 $roles = wp_roles()->get_names();
220 $result = array();
221
222 foreach ( $roles as $slug => $name ) {
223 $result[] = array(
224 'value' => $slug,
225 'label' => translate_user_role( $name ),
226 );
227 }
228
229 return $result;
230 }
231
232 /**
233 * Get all public post types as value/label pairs.
234 *
235 * @since 0.0.5
236 * @return array
237 */
238 private static function get_post_types() {
239 $types = get_post_types( array( 'public' => true ), 'objects' );
240 $result = array();
241
242 foreach ( $types as $slug => $type ) {
243 if ( 'attachment' === $slug ) {
244 continue;
245 }
246 $result[] = array(
247 'value' => $slug,
248 'label' => $type->labels->singular_name,
249 );
250 }
251
252 return $result;
253 }
254 }
255