PluginProbe
Extendify / 2.2.2
Extendify v2.2.2
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / app / Agent / Admin.php

Admin.php in Extendify 2.2.2, at app/Agent/Admin.php

348 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Admin.
5 */
6
7 namespace Extendify\Agent;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Agent\Controllers\ChatHistoryController;
12 use Extendify\Agent\Controllers\TourController;
13 use Extendify\Agent\Controllers\WorkflowHistoryController;
14 use Extendify\Config;
15 use Extendify\Shared\Services\Escaper;
16 use Extendify\Agent\TagBlocks;
17 use Extendify\Agent\TagTemplateParts;
18 use Extendify\Agent\Controllers\SiteNavigationController;
19 use Extendify\Shared\DataProvider\ProductsData;
20
21 /**
22 * This class handles any file loading for the admin area.
23 */
24 class Admin
25 {
26 /**
27 * Adds various actions to set up the page
28 *
29 * @return void
30 */
31 public function __construct()
32 {
33 \add_action('admin_enqueue_scripts', [$this, 'loadScriptsAndStyles']);
34 \add_action('wp_enqueue_scripts', [$this, 'loadScriptsAndStyles']);
35 ChatHistoryController::init();
36 WorkflowHistoryController::init();
37
38 // Tag blocks so we can identify them later
39 TagBlocks::init();
40 TagTemplateParts::init();
41
42 // Add the site navigation ids to the navigation blocks
43 SiteNavigationController::init();
44 }
45
46 /**
47 * Adds various JS scripts and styles
48 *
49 * @return void
50 */
51 public function loadScriptsAndStyles()
52 {
53 $version = constant('EXTENDIFY_DEVMODE') ? uniqid() : Config::$version;
54 $scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . Config::$assetManifest['extendify-agent.php'];
55 $fallback = [
56 'dependencies' => [],
57 'version' => $version,
58 ];
59 $scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback;
60
61 foreach ($scriptAsset['dependencies'] as $style) {
62 \wp_enqueue_style($style);
63 }
64
65 \wp_enqueue_script(
66 Config::$slug . '-agent-scripts',
67 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-agent.js'],
68 array_merge([Config::$slug . '-shared-scripts'], $scriptAsset['dependencies']),
69 $scriptAsset['version'],
70 true
71 );
72
73 $context = [
74 'adminPage' => function_exists('get_current_screen') && ($screen = get_current_screen())
75 ? \esc_attr($screen->id)
76 : null,
77 'postId' => (int) $this->getCurrentPostId(),
78 'postTitle' => \esc_attr(\get_the_title($this->getCurrentPostId())),
79 'postType' => \esc_attr(\get_post_type($this->getCurrentPostId())),
80 'postUrl' => \esc_url(\get_permalink($this->getCurrentPostId())),
81 'isFrontPage' => (bool) \is_front_page(),
82 'postStatus' => \esc_attr(\get_post_status((int) $this->getCurrentPostId())),
83 'isBlogPage' => (bool) \is_home(),
84 'themeSlug' => \esc_attr(\wp_get_theme()->get_stylesheet()),
85 'hasThemeVariations' => (bool) $this->hasThemeVariations(),
86 'isBlockTheme' => function_exists('wp_is_block_theme') ? (bool) wp_is_block_theme() : false,
87 'wordPressVersion' => \esc_attr(\get_bloginfo('version')),
88 'usingBlockEditor' => function_exists('use_block_editor_for_post') ?
89 (bool) use_block_editor_for_post($this->getCurrentPostId()) :
90 false,
91 'activePlugins' => array_values(\get_option('active_plugins', [])),
92 ];
93 $recommendations = ProductsData::get() ?? [];
94 $pluginRecommendations = array_filter($recommendations, function ($item) {
95 return in_array('ai-agent', $item['slots'] ?? [], true) && $item['ctaType'] === 'plugin';
96 });
97 $mappedPluginRecommendations = array_values(array_map(function ($item) {
98 return [
99 'title' => $item['title'] ?? '',
100 'slug' => $item['ctaPluginSlug'] ?? $item['slug'] ?? '',
101 'description' => $item['aiDescription'] ?? $item['description'] ?? '',
102 ];
103 }, $pluginRecommendations));
104 $agentContext = [
105 'availableAdminPages' => get_option('_transient_extendify_admin_pages_menu', []),
106 'pluginRecommendations' => $mappedPluginRecommendations,
107 ];
108 $abilities = [
109 'canEditPost' => (bool) \current_user_can('edit_post', \get_queried_object_id()),
110 // TODO: this may be true for a user, while they still can't edit every post
111 // So we would need to clarify this in the instructions, and
112 // include a step that fetches the page they want to edit
113 'canEditPosts' => (bool) \current_user_can('edit_posts'),
114 'canEditThemes' => (bool) \current_user_can('edit_theme_options'),
115 'canActivatePlugins' => (bool) \current_user_can('activate_plugins'),
116 'canInstallPlugins' => (bool) \current_user_can('install_plugins'),
117 'canEditUsers' => (bool) \current_user_can('edit_users'),
118 'canEditSettings' => (bool) \current_user_can('manage_options'),
119 'canUploadMedia' => (bool) \current_user_can('upload_files'),
120 ];
121
122 \wp_add_inline_script(
123 Config::$slug . '-agent-scripts',
124 'window.extAgentData = ' . \wp_json_encode([
125 // Add context about where they are
126 'context' => $context,
127 // Context that the Agent might need when returning a response,
128 // but not for handling the workflow.
129 'agentContext' => $agentContext,
130 // List of abilities the AI can perform for this user.
131 // For example, we could check whether their theme has variations.
132 'abilities' => $abilities,
133 // List of suggestions the AI can make for this user.
134 // For example, we could check whether they need to set up a specific plugin.
135 'suggestions' => $this->getSuggestions($context, $abilities),
136 'chatHistory' => Escaper::recursiveEscAttr(ChatHistoryController::getChatHistory()),
137 'workflowHistory' => Escaper::recursiveEscAttr(WorkflowHistoryController::getWorkflowHistory()),
138 'userData' => [
139 'tourData' => \wp_json_encode(TourController::get()->get_data()),
140 ],
141 ]),
142 'before'
143 );
144
145 \wp_set_script_translations(
146 Config::$slug . '-agent-scripts',
147 'extendify-local',
148 EXTENDIFY_PATH . 'languages/js'
149 );
150
151 \wp_enqueue_style(
152 Config::$slug . '-agent-styles',
153 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-agent.css'],
154 [],
155 Config::$version,
156 'all'
157 );
158 }
159 /**
160 * Get the current post ID based on the context.
161 *
162 * @return int
163 */
164 private function getCurrentPostId()
165 {
166 if (is_admin() && function_exists('get_current_screen')) {
167 $screen = get_current_screen();
168 if ($screen && $screen->base === 'post') {
169 global $post;
170 if ($post) {
171 return (int) $post->ID;
172 }
173 }
174 }
175 if (\is_front_page()) {
176 return (\get_option('show_on_front') === 'page') ? (int) \get_option('page_on_front') : 0;
177 }
178 if (\is_home()) {
179 return (int) \get_option('page_for_posts');
180 }
181 return (int) \get_queried_object_id();
182 }
183
184 /**
185 * Scan the style dirs to locate if they have variations.
186 * Ported from here:
187 * https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/class-wp-theme-json-resolver.php#L810
188 *
189 * @return bool
190 */
191 private function hasThemeVariations()
192 {
193 $base_directory = get_stylesheet_directory() . '/styles';
194 $template_directory = get_template_directory() . '/styles';
195
196 if (is_dir($base_directory) && glob($base_directory . '/*.json', GLOB_NOSORT)) {
197 return true;
198 }
199
200 // Only check parent if it's different from child
201 if (
202 $template_directory !== $base_directory &&
203 is_dir($template_directory) &&
204 glob($template_directory . '/*.json', GLOB_NOSORT)
205 ) {
206 return true;
207 }
208
209 return false;
210 }
211
212 /**
213 * Get suggestions for the user.
214 *
215 * @param array $context - The context of the current page and site.
216 * @param array $abilities - The abilities of the user.
217 * @return array
218 */
219 private function getSuggestions($context, $abilities)
220 {
221 $suggestions = [
222 [
223 'icon' => 'video',
224 'message' => __('What tours are available?', 'extendify-local'),
225 'workflowId' => 'list-tours',
226 ]
227 ];
228
229 if ($context['postStatus']) {
230 $suggestions [] = [
231 'icon' => ($context['postStatus'] === 'draft') ? 'published' : 'drafts',
232 'message' => ($context['postStatus'] === 'draft')
233 ? __('Publish this page', 'extendify-local')
234 : __('Unpublish this page', 'extendify-local'),
235 'workflowId' => 'update-post-status',
236 ];
237 }
238
239 if ($abilities['canEditSettings']) {
240 $suggestions[] = [
241 'icon' => 'edit',
242 'message' => __('I want to change my site title', 'extendify-local'),
243 "feature" => true,
244 'workflowId' => 'edit-wp-setting',
245 ];
246
247 $suggestions[] = [
248 'icon' => 'typography',
249 'message' => __('I want to change my theme fonts', 'extendify-local'),
250 "feature" => true,
251 'workflowId' => 'change-theme-fonts-variation',
252 ];
253 }
254
255 // If they have theme variations, suggest they can change the theme styling.
256 if ($context['hasThemeVariations']) {
257 $suggestions[] = [
258 'icon' => 'styles',
259 'message' => __('I want to change my theme styling', 'extendify-local'),
260 "feature" => true,
261 'workflowId' => 'change-theme-variation',
262 ];
263 }
264
265 if ($context['postId'] && $abilities['canEditPost'] && $context['usingBlockEditor']) {
266 $suggestions[] = [
267 'icon' => 'edit',
268 'message' => __('Edit text on this page', 'extendify-local'),
269 "feature" => true,
270 'workflowId' => 'edit-post-strings',
271 ];
272 }
273
274 if ($abilities['canEditPost']) {
275 $suggestions[] = [
276 'icon' => 'help',
277 'message' => __('How can I create a post?', 'extendify-local'),
278 'workflowId' => 'answer-general-questions',
279 ];
280 $suggestions[] = [
281 'icon' => 'help',
282 'message' => __('How can I create a page?', 'extendify-local'),
283 'workflowId' => 'create-page',
284 ];
285 }
286
287 if ($abilities['canActivatePlugins']) {
288 $suggestions[] = [
289 'icon' => 'help',
290 'message' => __('How can I install a plugin?', 'extendify-local'),
291 'workflowId' => 'recommend-plugins',
292 ];
293 }
294
295 if ($abilities['canEditThemes']) {
296 $suggestions[] = [
297 'icon' => 'help',
298 'message' => __('How can I change my theme?', 'extendify-local'),
299 'workflowId' => 'answer-general-questions',
300 ];
301 $suggestions[] = [
302 'icon' => 'help',
303 'message' => __('How can I change the site footer?', 'extendify-local'),
304 'workflowId' => 'answer-general-questions',
305 ];
306 $suggestions[] = [
307 'icon' => 'help',
308 'message' => __('How can I change the site header?', 'extendify-local'),
309 'workflowId' => 'answer-general-questions',
310 ];
311 }
312
313 if ($abilities['canUploadMedia']) {
314 $suggestions[] = [
315 'icon' => 'help',
316 'message' => __('How can I upload an image?', 'extendify-local'),
317 'workflowId' => 'answer-general-questions',
318 ];
319 $suggestions[] = [
320 'icon' => 'help',
321 'message' => __('How can I change the site icon?', 'extendify-local'),
322 'workflowId' => 'answer-general-questions',
323 ];
324 }
325
326 if ($abilities['canEditSettings']) {
327 $suggestions[] = [
328 'icon' => 'help',
329 'message' => __('How can I change my site title?', 'extendify-local'),
330 'workflowId' => 'edit-wp-setting',
331 ];
332 $suggestions[] = [
333 'icon' => 'help',
334 'message' => __('How can I change my site tagline?', 'extendify-local'),
335 'workflowId' => 'edit-wp-setting',
336 ];
337 $suggestions[] = [
338 'icon' => 'help',
339 'message' => __('How can I change my site language?', 'extendify-local'),
340 'workflowId' => 'answer-general-questions',
341 ];
342 }
343
344 shuffle($suggestions);
345 return $suggestions;
346 }
347 }
348