PluginProbe
Page Builder: Pagelayer – Drag and Drop website builder / 2.2.1
Page Builder: Pagelayer – Drag and Drop website builder v2.2.1
2.2.1 2.2.0 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 trunk 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 1.0.0 1.0.2 1.0.3 1.0.4 1.0.5 All 129 releases
pagelayer / main / abilities.php

abilities.php in Page Builder: Pagelayer – Drag and Drop website builder 2.2.1, at main/abilities.php

523 lines 27.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if(!defined('PAGELAYER_VERSION')) {
4 exit('Hacking Attempt !');
5 }
6
7 class Pagelayer_Abilities {
8
9 static $APP_PASSWORD_NAME = 'Pagelayer AI Agents';
10 static $APP_PASSWORD_APP_ID = 'pagelayer-mcp';
11 static $ABILITIES_ENDPOINT = '/wp-json/wp-abilities/v1/abilities';
12 static $MCP_SERVER_ENDPOINT = '/wp-json/mcp/mcp-adapter-default-server';
13 static $TEST_STATUS_META = 'pagelayer_mcp_test_status';
14
15 public static function render_page() {
16 // Include settings header
17 include_once(PAGELAYER_DIR.'/main/settings.php');
18
19 $capabilities_ok = current_user_can('manage_options');
20 if (!$capabilities_ok) {
21 echo '<div class="wrap"><h1>' . __('Unauthorized', 'pagelayer') . '</h1></div>';
22 return;
23 }
24
25 pagelayer_page_header(__('AI Agents Connection', 'pagelayer'));
26
27 $abilities_api_available = function_exists('wp_register_ability');
28 $adapter_active = class_exists('\\WP\\MCP\\Core\\McpAdapter');
29 $adapter_installed = '' !== self::get_installed_mcp_adapter_file();
30 $has_app_password = self::current_user_has_mcp_app_password();
31 $test_status = self::get_test_connection_status();
32 $test_ok = !empty($test_status['ok']);
33 $test_message = !empty($test_status['message']) ? $test_status['message'] : '';
34 $site_url = trailingslashit(rtrim(home_url(), '/'));
35 $endpoint_url = $site_url . ltrim(self::$ABILITIES_ENDPOINT, '/');
36 $mcp_server_url = $site_url . ltrim(self::$MCP_SERVER_ENDPOINT, '/');
37 $username = function_exists('wp_get_current_user') ? wp_get_current_user()->user_login : '';
38 $app_pass_placeholder = __('your-application-password', 'pagelayer');
39 $user_placeholder = $username ?: __('<your-username>', 'pagelayer');
40
41 $total_steps = 4;
42 $done_count = 0;
43 $next_step = '';
44 if(!$abilities_api_available){
45 $next_step = __('Abilities API registration', 'pagelayer');
46 }elseif(!$adapter_active){
47 $done_count++;
48 $next_step = __('MCP Adapter installation', 'pagelayer');
49 }elseif(!$has_app_password){
50 $done_count += 2;
51 $next_step = __('Application Password generation', 'pagelayer');
52 }elseif(!$test_ok){
53 $done_count += 3;
54 $next_step = __('Connection test', 'pagelayer');
55 }else{
56 $done_count += 4;
57 }
58
59 $stepper_width = 0;
60 if ($done_count > 1) {
61 $stepper_width = ($done_count - 1) / 3 * 100;
62 }
63
64 $adapter_label = $adapter_active ? __('Active', 'pagelayer') : ($adapter_installed ? __('Inactive', 'pagelayer') : __('Not installed', 'pagelayer'));
65 ?>
66
67 <div class="pagelayer-ai">
68
69 <!-- Main 2-column layout -->
70 <div class="pagelayer-ai-layout">
71
72 <!-- LEFT COLUMN -->
73 <div class="pagelayer-ai-main">
74
75 <!-- Setup checklist card -->
76 <div class="pagelayer-ai-card">
77 <div class="pagelayer-ai-card-head">
78 <h2 class="pagelayer-ai-h2"><?php esc_html_e('Setup', 'pagelayer'); ?></h2>
79 <div class="pagelayer-ai-meta"><?php echo esc_html(sprintf(__('%1$d of %2$d steps complete', 'pagelayer'), $done_count, $total_steps)); ?></div>
80 </div>
81
82 <!-- Connector progress bar -->
83 <div class="pagelayer-ai-stepper" role="list">
84 <div class="pagelayer-ai-stepper-track" aria-hidden="true">
85 <span class="pagelayer-ai-stepper-fill" style="width: <?php echo esc_attr($stepper_width); ?>%;"></span>
86 </div>
87 <?php
88 $chips = [
89 ['label' => __('Abilities', 'pagelayer'), 'state' => $abilities_api_available ? 'done' : 'todo'],
90 ['label' => __('MCP Adapter', 'pagelayer'),'state' => $adapter_active ? 'done' : 'todo'],
91 ['label' => __('Password', 'pagelayer'), 'state' => $has_app_password ? 'done' : 'todo'],
92 ['label' => __('Test', 'pagelayer'), 'state' => $test_ok ? 'done' : 'todo'],
93 ];
94 foreach($chips as $idx => $chip){
95 echo '<div class="pagelayer-ai-stepper-node pagelayer-ai-stepper-node-' . esc_attr($chip['state']) . '" role="listitem">';
96 echo '<span class="pagelayer-ai-stepper-circle">' . ($chip['state'] === 'done' ? '&#10003;' : esc_html((string)($idx + 1))) . '</span>';
97 echo '<span class="pagelayer-ai-stepper-label">' . esc_html($chip['label']) . '</span>';
98 echo '</div>';
99 }
100 ?>
101 </div>
102
103 <?php
104 if($next_step){
105 echo '<p class="pagelayer-ai-nextup">' . sprintf(__('Next up: %s', 'pagelayer'), '<strong>' . esc_html($next_step) . '</strong>') . '</p>';
106 }else{
107 echo '<p class="pagelayer-ai-nextup pagelayer-ai-nextup-done">' . __('All steps complete. Your AI client can now connect.', 'pagelayer') . '</p>';
108 }
109 ?>
110
111 <!-- Step rows -->
112 <div class="pagelayer-ai-rows">
113
114 <!-- Step 1: Abilities registered -->
115 <div class="pagelayer-ai-row <?php echo ($abilities_api_available ? 'pagelayer-ai-row-done' : ''); ?>" data-step="1">
116 <button type="button" class="pagelayer-ai-row-toggle" aria-expanded="true">
117 <span class="pagelayer-ai-row-indicator"><?php echo ($abilities_api_available ? '&#10003;' : '1'); ?></span>
118 <span class="pagelayer-ai-row-text">
119 <span class="pagelayer-ai-row-title"><?php esc_html_e('Pagelayer abilities registered', 'pagelayer'); ?></span>
120 <span class="pagelayer-ai-row-sub"><?php esc_html_e('Exposed via the WordPress 6.9+ Abilities API', 'pagelayer'); ?></span>
121 </span>
122 <span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($abilities_api_available ? 'success' : 'warning'); ?>"><?php echo ($abilities_api_available ? esc_html__('Ready', 'pagelayer') : esc_html__('Requires WP 6.9+', 'pagelayer')); ?></span>
123 </button>
124 <div class="pagelayer-ai-row-body">
125 <?php
126 if($abilities_api_available){
127 echo '<p>' . esc_html__('Pagelayer registers building and styling abilities. They are picked up automatically by any installed MCP adapter and surfaced to AI clients as tools.', 'pagelayer') . '</p>';
128 echo wp_kses_post( self::render_abilities_list() );
129 }else{
130 echo '<p>' . esc_html__('The WordPress Abilities API ships in WordPress 6.9+. Pagelayer can still work today — installing the MCP Adapter backfills the API so your AI client can connect immediately. Consider updating WordPress for native abilities support.', 'pagelayer') . '</p>';
131 }
132 ?>
133 </div>
134 </div>
135
136 <!-- Step 2: MCP Adapter -->
137 <?php
138 $adapter_state = $adapter_active ? 'done' : ($adapter_installed ? 'progress' : 'todo');
139 ?>
140 <div class="pagelayer-ai-row <?php echo ($adapter_active ? 'pagelayer-ai-row-done' : ''); ?>" data-step="2">
141 <button type="button" class="pagelayer-ai-row-toggle" aria-expanded="<?php echo ($adapter_active ? 'false' : 'true'); ?>">
142 <span class="pagelayer-ai-row-indicator"><?php echo ($adapter_active ? '&#10003;' : '2'); ?></span>
143 <span class="pagelayer-ai-row-text">
144 <span class="pagelayer-ai-row-title"><?php esc_html_e('MCP Adapter installed', 'pagelayer'); ?></span>
145 <span class="pagelayer-ai-row-sub"><?php esc_html_e('Bridges your site to MCP-compatible AI clients', 'pagelayer'); ?></span>
146 </span>
147 <span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($adapter_active ? 'success' : 'warning'); ?>"><?php echo esc_html($adapter_label); ?></span>
148 </button>
149 <div class="pagelayer-ai-row-body">
150 <?php
151 if($adapter_active){
152 echo '<p>' . esc_html__('The MCP Adapter plugin is installed and active. Your site now exposes an MCP server that AI clients can connect to.', 'pagelayer') . '</p>';
153 }elseif($adapter_installed){
154 echo '<p>' . esc_html__('The MCP Adapter is installed but not active. Activate it to enable the MCP server.', 'pagelayer') . '</p>';
155 echo '<button type="button" class="button button-primary pagelayer-ai-btn" id="pagelayer-ai-adapter-btn" data-action="activate">' . esc_html__('Activate MCP Adapter', 'pagelayer') . '</button>';
156 }else{
157 echo '<p>' . esc_html__('Install the official WordPress MCP Adapter plugin with a single click. It bridges your site to any MCP-compatible AI client.', 'pagelayer') . '</p>';
158 echo '<button type="button" class="button button-primary pagelayer-ai-btn" id="pagelayer-ai-adapter-btn" data-action="install">' . esc_html__('Install MCP Adapter', 'pagelayer') . '</button>';
159 }
160 ?>
161 <div id="pagelayer-ai-adapter-msg" style="margin-top:10px; font-size:13px;"></div>
162 </div>
163 </div>
164
165 <!-- Step 3: Application Password -->
166 <div class="pagelayer-ai-row <?php echo ($has_app_password ? 'pagelayer-ai-row-done' : ''); ?>" data-step="3">
167 <button type="button" class="pagelayer-ai-row-toggle" aria-expanded="<?php echo ($has_app_password ? 'false' : 'true'); ?>">
168 <span class="pagelayer-ai-row-indicator"><?php echo ($has_app_password ? '&#10003;' : '3'); ?></span>
169 <span class="pagelayer-ai-row-text">
170 <span class="pagelayer-ai-row-title"><?php esc_html_e('Application Password generated', 'pagelayer'); ?></span>
171 <span class="pagelayer-ai-row-sub"><?php esc_html_e('Authenticates your AI client against your site', 'pagelayer'); ?></span>
172 </span>
173 <span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($has_app_password ? 'success' : 'warning'); ?>"><?php echo ($has_app_password ? esc_html__('Generated', 'pagelayer') : esc_html__('Action needed', 'pagelayer')); ?></span>
174 </button>
175 <div class="pagelayer-ai-row-body">
176 <p><?php esc_html_e('Generate a WordPress Application Password for your AI client. The password is shown only once — copy it somewhere safe.', 'pagelayer'); ?></p>
177 <div class="pagelayer-ai-app-pass-fields" id="pagelayer-ai-pass-container" style="display:none;">
178 <label class="pagelayer-ai-field"><span><?php esc_html_e('Username', 'pagelayer'); ?></span><input type="text" readonly class="pagelayer-ai-input" value="<?php echo esc_attr($username); ?>" /></label>
179 <label class="pagelayer-ai-field"><span><?php esc_html_e('Application Password', 'pagelayer'); ?></span><input type="text" readonly class="pagelayer-ai-input" id="pagelayer-ai-pass-input" value="" /></label>
180 <a class="pagelayer-ai-profile-link" href="<?php echo esc_url(admin_url('profile.php#application-passwords-section')); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Manage in profile', 'pagelayer'); ?> &nbsp;&rarr;</a>
181 </div>
182 <button type="button" class="button button-secondary pagelayer-ai-btn" id="pagelayer-ai-pass-btn"><?php echo ($has_app_password ? esc_html__('Generate a new Application Password', 'pagelayer') : esc_html__('Generate Application Password', 'pagelayer')); ?></button>
183 </div>
184 </div>
185
186 <!-- Step 4: Test Connection -->
187 <div class="pagelayer-ai-row <?php echo ($test_ok ? 'pagelayer-ai-row-done' : ($test_message ? 'pagelayer-ai-row-progress' : '')); ?>" data-step="4">
188 <button type="button" class="pagelayer-ai-row-toggle" aria-expanded="<?php echo ($test_ok ? 'false' : 'true'); ?>">
189 <span class="pagelayer-ai-row-indicator"><?php echo ($test_ok ? '&#10003;' : '4'); ?></span>
190 <span class="pagelayer-ai-row-text">
191 <span class="pagelayer-ai-row-title"><?php esc_html_e('Test the connection', 'pagelayer'); ?></span>
192 <span class="pagelayer-ai-row-sub"><?php esc_html_e('Verify authentication and ability discovery', 'pagelayer'); ?></span>
193 </span>
194 <span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($test_ok ? 'success' : 'neutral'); ?>" id="pagelayer-ai-test-status"><?php echo ($test_ok ? esc_html__('Verified', 'pagelayer') : esc_html__('Pending', 'pagelayer')); ?></span>
195 </button>
196 <div class="pagelayer-ai-row-body">
197 <p><?php esc_html_e('Verify that your AI client can authenticate against your site and discover Pagelayer abilities.', 'pagelayer'); ?></p>
198 <button type="button" class="button button-secondary pagelayer-ai-btn" id="pagelayer-ai-test-btn"><?php esc_html_e('Test connection', 'pagelayer'); ?></button>
199 <div id="pagelayer-ai-test-result" class="pagelayer-ai-test-result <?php echo $test_ok ? 'pagelayer-ai-test-result-ok' : 'pagelayer-ai-test-result-error'; ?>" style="<?php echo $test_message ? '' : 'display:none;'; ?>">
200 <?php echo $test_message ? esc_html(($test_ok ? __('Success: ', 'pagelayer') : __('Failed: ', 'pagelayer')) . $test_message) : ''; ?>
201 </div>
202 </div>
203 </div>
204
205 </div> <!-- .pagelayer-ai-rows -->
206 </div> <!-- .pagelayer-ai-card -->
207
208 <!-- AI Client Configuration -->
209 <div class="pagelayer-ai-card">
210 <div class="pagelayer-ai-card-head">
211 <h2 class="pagelayer-ai-h2"><?php esc_html_e('Connect to AI Client', 'pagelayer'); ?></h2>
212 <div class="pagelayer-ai-meta"><?php esc_html_e('Pick a client, copy the snippet, paste it in the configuration file.', 'pagelayer'); ?></div>
213 </div>
214
215 <script type="application/json" id="pagelayer-abilities-json-data"><?php
216 echo wp_json_encode([
217 'siteUrl' => $site_url,
218 'mcpServerUrl' => $mcp_server_url,
219 'username' => $username,
220 'nonce' => wp_create_nonce('pagelayer_mcp_nonce'),
221 ]);
222 ?></script>
223
224 <div class="pagelayer-ai-segmented" role="tablist">
225 <button type="button" class="pagelayer-ai-segmented-btn active" data-client="claude">Claude Desktop</button>
226 <button type="button" class="pagelayer-ai-segmented-btn" data-client="claude_code">Claude Code CLI</button>
227 <button type="button" class="pagelayer-ai-segmented-btn" data-client="cursor">Cursor</button>
228 <button type="button" class="pagelayer-ai-segmented-btn" data-client="grok_cli">Grok CLI</button>
229 <button type="button" class="pagelayer-ai-segmented-btn" data-client="vscode">VS Code</button>
230 <button type="button" class="pagelayer-ai-segmented-btn" data-client="antigravity">Antigravity</button>
231 <button type="button" class="pagelayer-ai-segmented-btn" data-client="opencode">OpenCode</button>
232 <button type="button" class="pagelayer-ai-segmented-btn" data-client="gemini_cli">Gemini CLI <span class="pagelayer-deprecated-badge"><?php esc_html_e('Deprecated', 'pagelayer'); ?></span></button>
233 </div>
234
235 <!-- Client panel -->
236 <div class="pagelayer-ai-client-panel">
237 <div class="pagelayer-ai-client-meta">
238 <span class="pagelayer-ai-client-path-label"><?php esc_html_e('Config file path:', 'pagelayer'); ?></span>
239 <code class="pagelayer-ai-client-path-value" id="pagelayer-ai-filepath"></code>
240 </div>
241
242 <div class="pagelayer-ai-snippet-wrap">
243 <div class="pagelayer-ai-snippet-header">
244 <span class="pagelayer-ai-snippet-title" id="pagelayer-ai-snippet-title"><?php esc_html_e('JSON Configuration', 'pagelayer'); ?></span>
245 <button type="button" class="pagelayer-ai-copy-btn" id="pagelayer-ai-copy-snippet-btn">
246 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
247 <span id="pagelayer-ai-copy-snippet-btn-label"><?php esc_html_e('Copy snippet', 'pagelayer'); ?></span>
248 </button>
249 </div>
250 <pre class="pagelayer-ai-snippet" id="pagelayer-ai-snippet" tabindex="0"></pre>
251 </div>
252
253 <ol class="pagelayer-ai-instructions" id="pagelayer-ai-instructions-list"></ol>
254 </div>
255 </div>
256
257 </div> <!-- .pagelayer-ai-main -->
258
259 <!-- RIGHT COLUMN (sidebar) -->
260 <div class="pagelayer-ai-side">
261
262 <!-- Status card -->
263 <div class="pagelayer-ai-card pagelayer-ai-side-card">
264 <h3 class="pagelayer-ai-h3"><?php esc_html_e('Site status', 'pagelayer'); ?></h3>
265 <dl class="pagelayer-ai-status-list">
266 <div>
267 <dt><?php esc_html_e('Abilities API', 'pagelayer'); ?></dt>
268 <dd><span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($abilities_api_available ? 'success' : 'neutral'); ?>"><?php echo ($abilities_api_available ? esc_html__('Available', 'pagelayer') : esc_html__('Unavailable', 'pagelayer')); ?></span></dd>
269 </div>
270 <div>
271 <dt><?php esc_html_e('MCP Adapter', 'pagelayer'); ?></dt>
272 <dd><span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($adapter_active ? 'success' : ($adapter_installed ? 'warning' : 'neutral')); ?>"><?php echo esc_html($adapter_label); ?></span></dd>
273 </div>
274 <div>
275 <dt><?php esc_html_e('Application Password', 'pagelayer'); ?></dt>
276 <dd><span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($has_app_password ? 'success' : 'neutral'); ?>"><?php echo ($has_app_password ? esc_html__('Generated', 'pagelayer') : esc_html__('Not generated', 'pagelayer')); ?></span></dd>
277 </div>
278 <div>
279 <dt><?php esc_html_e('Last test', 'pagelayer'); ?></dt>
280 <dd><span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo ($test_ok ? 'success' : ($test_message ? 'error' : 'neutral')); ?>"><?php echo ($test_ok ? esc_html__('Tested', 'pagelayer') : ($test_message ? esc_html__('Failed', 'pagelayer') : esc_html__('Never', 'pagelayer'))); ?></span></dd>
281 </div>
282 </dl>
283 </div>
284
285 <!-- Quick tips card -->
286 <div class="pagelayer-ai-card pagelayer-ai-side-card">
287 <h3 class="pagelayer-ai-h3"><?php esc_html_e('Quick tips', 'pagelayer'); ?></h3>
288 <ul class="pagelayer-ai-tip-list">
289 <li><?php esc_html_e('Generate one Application Password per AI client so you can revoke them individually.', 'pagelayer'); ?></li>
290 <li><?php esc_html_e('The password is shown only once. Copy it before navigating away.', 'pagelayer'); ?></li>
291 <li><?php esc_html_e('Application Passwords are stored in your user profile and can be revoked at any time.', 'pagelayer'); ?></li>
292 <li><?php esc_html_e('Each ability is permission-gated - your AI client can only act within your user capabilities.', 'pagelayer'); ?></li>
293 </ul>
294 </div>
295
296 <!-- Image search (Pexels) card -->
297 <?php
298 $pexels_key = get_option('pagelayer_pexels_api_key', '');
299 $pexels_configured = !empty($pexels_key);
300 ?>
301 <div class="pagelayer-ai-card pagelayer-ai-side-card">
302 <h3 class="pagelayer-ai-h3"><?php esc_html_e('Image Search (Pexels)', 'pagelayer'); ?></h3>
303 <p class="pagelayer-ai-side-desc"><?php esc_html_e('Lets the AI pull real, topically relevant photos instead of generic placeholders when it builds pages. Get a free key from pexels.com/api.', 'pagelayer'); ?></p>
304 <label class="pagelayer-ai-field">
305 <span><?php esc_html_e('Pexels API Key', 'pagelayer'); ?></span>
306 <input type="text" class="pagelayer-ai-input" id="pagelayer-ai-pexels-key" placeholder="<?php echo $pexels_configured ? esc_attr(str_repeat('•', 8) . substr($pexels_key, -4)) : esc_attr__('your-pexels-api-key', 'pagelayer'); ?>" value="" />
307 </label>
308 <button type="button" class="button button-secondary pagelayer-ai-btn" id="pagelayer-ai-pexels-save-btn"><?php echo $pexels_configured ? esc_html__('Update Key', 'pagelayer') : esc_html__('Save Key', 'pagelayer'); ?></button>
309 <span class="pagelayer-ai-pill pagelayer-ai-pill-<?php echo $pexels_configured ? 'success' : 'neutral'; ?>" id="pagelayer-ai-pexels-status" style="margin-left:8px;"><?php echo $pexels_configured ? esc_html__('Configured', 'pagelayer') : esc_html__('Not configured', 'pagelayer'); ?></span>
310 <div id="pagelayer-ai-pexels-msg" style="margin-top:8px; font-size:13px;"></div>
311 </div>
312
313 <!-- Resources card -->
314 <div class="pagelayer-ai-card pagelayer-ai-side-card">
315 <h3 class="pagelayer-ai-h3"><?php esc_html_e('Resources', 'pagelayer'); ?></h3>
316 <ul class="pagelayer-ai-resource-list">
317 <li><a href="https://pagelayer.com/docs" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Pagelayer documentation', 'pagelayer'); ?></a><span class="pagelayer-ai-resource-sub"><?php esc_html_e('Guides', 'pagelayer'); ?></span></li>
318 <li><a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Model Context Protocol', 'pagelayer'); ?></a><span class="pagelayer-ai-resource-sub"><?php esc_html_e('What MCP is and how it works', 'pagelayer'); ?></span></li>
319 <li><a href="https://github.com/WordPress/mcp-adapter" target="_blank" rel="noopener noreferrer"><?php esc_html_e('WordPress MCP Adapter', 'pagelayer'); ?></a><span class="pagelayer-ai-resource-sub"><?php esc_html_e('Official plugin repository', 'pagelayer'); ?></span></li>
320 </ul>
321 </div>
322
323 </div> <!-- .pagelayer-ai-side -->
324 </div> <!-- .pagelayer-ai-layout -->
325 </div> <!-- .pagelayer-ai -->
326
327 <?php
328 pagelayer_page_footer();
329 }
330
331 static function render_abilities_list() {
332 $groups = self::get_registered_abilities();
333
334 if(empty($groups)){
335 return '<p class="pagelayer-ai-empty">' . esc_html__('No Pagelayer abilities are currently exposed. Activate the MCP Adapter to surface them.', 'pagelayer') . '</p>';
336 }
337
338 $html = '<div class="pagelayer-ai-list">';
339 foreach($groups as $group => $items){
340 $html .= '<div class="pagelayer-ai-group">';
341 $html .= '<h4>' . esc_html($group) . '</h4>';
342 $html .= '<ul>';
343 foreach($items as $ability){
344 $html .= '<li>';
345 $html .= '<span class="pagelayer-ai-ability-name">' . esc_html($ability['label']) . '</span>';
346 if(!empty($ability['description'])){
347 $html .= '<span class="pagelayer-ai-ability-desc">' . esc_html($ability['description']) . '</span>';
348 }
349 $html .= '</li>';
350 }
351 $html .= '</ul>';
352 $html .= '</div>';
353 }
354 $html .= '</div>';
355 return $html;
356 }
357
358 static function get_registered_abilities(){
359 return [
360 __('Pages Management', 'pagelayer') => [
361 [
362 'label' => __('Create Page', 'pagelayer'),
363 'description' => __('Creates a new WordPress page built with Pagelayer.', 'pagelayer'),
364 ],
365 [
366 'label' => __('Update Page', 'pagelayer'),
367 'description' => __('Updates page title, builder data, and publish status.', 'pagelayer'),
368 ],
369 [
370 'label' => __('Get / List / Delete Pages', 'pagelayer'),
371 'description' => __('Inspect, list, and delete Pagelayer pages.', 'pagelayer'),
372 ],
373 [
374 'label' => __('Validate Page Layout', 'pagelayer'),
375 'description' => __('AI validation engine for widget compatibility, hierarchy, responsiveness, accessibility, and SEO.', 'pagelayer'),
376 ],
377 [
378 'label' => __('Duplicate / Publish Page', 'pagelayer'),
379 'description' => __('Clone pages with fresh element IDs and publish drafts.', 'pagelayer'),
380 ],
381 ],
382 __('Individual Posts Management', 'pagelayer') => [
383 [
384 'label' => __('Create Post', 'pagelayer'),
385 'description' => __('Creates a new blog post built with Pagelayer, specifying categories, tags, excerpt, and featured image.', 'pagelayer'),
386 ],
387 [
388 'label' => __('Update Post', 'pagelayer'),
389 'description' => __('Updates post layout, categories, tags, excerpt, and status.', 'pagelayer'),
390 ],
391 [
392 'label' => __('Get / List / Delete Posts', 'pagelayer'),
393 'description' => __('Inspect, list, publish, duplicate, and delete blog posts.', 'pagelayer'),
394 ],
395 ],
396 __('Widgets & Schemas', 'pagelayer') => [
397 [
398 'label' => __('List Widgets', 'pagelayer'),
399 'description' => __('List all registered Pagelayer widgets, display names, categories, icons, and nesting rules.', 'pagelayer'),
400 ],
401 [
402 'label' => __('Get Widget Schema & Examples', 'pagelayer'),
403 'description' => __('Retrieve control property schema and canonical pagelayer_data node examples.', 'pagelayer'),
404 ],
405 ],
406 __('Theme Templates', 'pagelayer') => [
407 [
408 'label' => __('Get / Create Theme Templates', 'pagelayer'),
409 'description' => __('Build header, footer, blog archive, single blog, search, 404, sidebar, popup, and WooCommerce templates.', 'pagelayer'),
410 ],
411 [
412 'label' => __('Update / Delete Templates', 'pagelayer'),
413 'description' => __('Update or delete theme templates and display condition rules.', 'pagelayer'),
414 ],
415 ],
416 __('Navigation Menus', 'pagelayer') => [
417 [
418 'label' => __('Get / Create Menus', 'pagelayer'),
419 'description' => __('Build the WordPress nav menus that the Primary Menu widget renders in headers, including Mega Menu dropdowns and theme location assignment.', 'pagelayer'),
420 ],
421 ],
422 __('Global Design System', 'pagelayer') => [
423 [
424 'label' => __('Get Theme Settings & Styles', 'pagelayer'),
425 'description' => __('Retrieve theme options, global colors/fonts, content width, and WooCommerce status.', 'pagelayer'),
426 ],
427 [
428 'label' => __('Presets & Catalogs', 'pagelayer'),
429 'description' => __('Get color palette presets, spacing scale presets, FontAwesome icon catalog, and font catalog.', 'pagelayer'),
430 ],
431 [
432 'label' => __('Search Real Images', 'pagelayer'),
433 'description' => __('Search Pexels by keyword for real, topically relevant photos to use in place of placeholders (requires a Pexels API key, configured below).', 'pagelayer'),
434 ],
435 ],
436 ];
437 }
438
439 static function get_installed_mcp_adapter_file(){
440 if(!function_exists('get_plugins')){
441 require_once ABSPATH . 'wp-admin/includes/plugin.php';
442 }
443
444 foreach(array_keys(get_plugins()) as $plugin_file){
445 if(0 === strpos($plugin_file, 'mcp-adapter/')){
446 return $plugin_file;
447 }
448 }
449 return '';
450 }
451
452 static function current_user_has_mcp_app_password(){
453 $user_id = get_current_user_id();
454
455 if(!$user_id || !class_exists('WP_Application_Passwords')){
456 return false;
457 }
458
459 foreach(\WP_Application_Passwords::get_user_application_passwords($user_id) as $app){
460 if(!empty($app['app_id']) && $app['app_id'] === self::$APP_PASSWORD_APP_ID){
461 return true;
462 }
463 }
464 return false;
465 }
466
467 static function get_test_connection_status(){
468 $user_id = get_current_user_id();
469 if(!$user_id){
470 return ['ok' => false, 'message' => ''];
471 }
472 $stored = get_user_meta($user_id, self::$TEST_STATUS_META, true);
473 if(!is_array($stored)){
474 return ['ok' => false, 'message' => ''];
475 }
476 return [
477 'ok' => !empty($stored['ok']),
478 'message' => !empty($stored['message']) ? $stored['message'] : '',
479 ];
480 }
481
482 public static function save_test_connection_status($ok, $message){
483 $user_id = get_current_user_id();
484 if(!$user_id){
485 return;
486 }
487 update_user_meta($user_id, self::$TEST_STATUS_META, [
488 'ok' => !empty($ok),
489 'message' => (string)$message,
490 ]);
491 }
492
493 static $MCP_ADAPTER_DOWNLOAD_URL = 'https://github.com/WordPress/mcp-adapter/releases/latest/download/mcp-adapter.zip';
494 static $MCP_ADAPTER_GITHUB_API = 'https://api.github.com/repos/WordPress/mcp-adapter/releases/latest';
495
496 static function get_mcp_adapter_download_url(){
497 $response = wp_remote_get(self::$MCP_ADAPTER_GITHUB_API, array(
498 'headers' => array(
499 'Accept' => 'application/vnd.github+json',
500 'User-Agent' => 'Pagelayer/' . PAGELAYER_VERSION,
501 ),
502 'timeout' => 30,
503 ));
504
505 if(is_wp_error($response)){
506 return self::$MCP_ADAPTER_DOWNLOAD_URL;
507 }
508
509 $body = wp_remote_retrieve_body($response);
510 $data = json_decode($body, true);
511
512 if(is_array($data) && !empty($data['assets'])){
513 foreach($data['assets'] as $asset){
514 if(!empty($asset['browser_download_url']) && false !== stripos($asset['name'], '.zip')){
515 return $asset['browser_download_url'];
516 }
517 }
518 }
519
520 return self::$MCP_ADAPTER_DOWNLOAD_URL;
521 }
522 }
523