PluginProbe
Extendify / 3.0.4
Extendify v3.0.4
3.2.1 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 All 127 releases
extendify / app / Shared / Admin.php

Admin.php in Extendify 3.0.4, at app/Shared/Admin.php

364 lines 14.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Help Center Script loader.
5 */
6
7 namespace Extendify\Shared;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Config;
12 use Extendify\PartnerData;
13 use Extendify\Shared\Controllers\UserSelectionController;
14 use Extendify\Shared\DataProvider\ResourceData;
15 use Extendify\Shared\Services\AdminMenuList;
16 use Extendify\Shared\Services\ApexDomain\ApexDomain;
17 use Extendify\Shared\Services\Escaper;
18 use Extendify\Shared\Services\PluginDependencies\SimplyBook;
19 use Extendify\SiteSettings;
20 use Extendify\Shared\Controllers\ImageGenerationController;
21 use Extendify\Shared\DataProvider\ProductsData;
22
23 /**
24 * This class handles any file loading for the admin area.
25 */
26
27 class Admin
28 {
29 /**
30 * Adds various actions to set up the page
31 *
32 * @return void
33 */
34 public function __construct()
35 {
36 \add_action('init', [$this, 'addExtraMetaFields']);
37 \add_action('admin_enqueue_scripts', [$this, 'loadGlobalScripts']);
38 \add_action('wp_enqueue_scripts', [$this, 'loadGlobalScripts']);
39 \add_action('wp_ajax_search-install-plugins', [$this, 'recordPluginsSearchTerms'], -1);
40 \add_action('rest_api_init', [$this, 'recordBlocksSearchTerms']);
41 \add_action('wp_ajax_query-themes', [$this, 'recordThemesSearchTerms'], -1);
42 AdminMenuList::init();
43 \add_action('simplybook_activation', [SimplyBook::class, 'getIndustryCode'], 10, 0);
44 }
45
46 // phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
47 /**
48 * Adds scripts to every page
49 *
50 * @return void
51 */
52 public function loadGlobalScripts()
53 {
54 \wp_enqueue_media();
55
56 $this->updateUserMeta();
57
58 $version = constant('EXTENDIFY_DEVMODE') ? uniqid() : Config::$version;
59
60 /**
61 * Enqueue shared JavaScript files if they exist in the asset manifest
62 * Ensures proper loading order: vendors -> common
63 */
64
65 // Enqueue the vendor chunk first.
66 if (isset(Config::$assetManifest['extendify-vendors.js'])) {
67 wp_enqueue_script(
68 'extendify-vendors',
69 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-vendors.js'],
70 [],
71 $version,
72 true
73 // Load in footer.
74 );
75 }
76
77 // Enqueue the common chunk next, dependent on vendors.
78 if (isset(Config::$assetManifest['extendify-common.js'])) {
79 wp_enqueue_script(
80 'extendify-common',
81 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-common.js'],
82 ['extendify-vendors'],
83 $version,
84 true
85 );
86 }
87
88 /*
89 * Loads a unique runtime generated by Webpack to manage all the other generated scripts.
90 * Without this runtime, the other Extendify scripts won't load.
91 */
92 $scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . Config::$assetManifest['extendify-runtime.php'];
93 $fallback = [
94 'dependencies' => [],
95 'version' => $version,
96 ];
97 $scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback;
98
99 foreach ($scriptAsset['dependencies'] as $style) {
100 \wp_enqueue_style($style);
101 }
102
103 \wp_enqueue_script(
104 Config::$slug . '-runtime-scripts',
105 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-runtime.js'],
106 $scriptAsset['dependencies'],
107 $scriptAsset['version'],
108 true
109 );
110
111 $scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . Config::$assetManifest['extendify-shared.php'];
112 $fallback = [
113 'dependencies' => [],
114 'version' => $version,
115 ];
116 $scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback;
117
118 foreach ($scriptAsset['dependencies'] as $style) {
119 \wp_enqueue_style($style);
120 }
121
122 \wp_enqueue_script(
123 Config::$slug . '-shared-scripts',
124 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-shared.js'],
125 $scriptAsset['dependencies'],
126 $scriptAsset['version'],
127 true
128 );
129
130 $partnerData = PartnerData::getPartnerData();
131 $userConsent = get_user_meta(get_current_user_id(), 'extendify_ai_consent', true);
132 $htmlAllowlist = [
133 'a' => [
134 'target' => [],
135 'href' => [],
136 'rel' => [],
137 ],
138 ];
139
140 if (!function_exists('get_plugins')) {
141 require_once ABSPATH . 'wp-admin/includes/plugin.php';
142 }
143
144 \wp_add_inline_script(
145 Config::$slug . '-shared-scripts',
146 'window.extSharedData = ' . \wp_json_encode([
147 'root' => \esc_url_raw(rest_url(Config::$slug . '/' . Config::$apiVersion)),
148 'homeUrl' => \esc_url_raw(\get_home_url()),
149 'adminUrl' => \esc_url_raw(\admin_url()),
150 'nonce' => \esc_attr(\wp_create_nonce('wp_rest')),
151 'devbuild' => (bool) constant('EXTENDIFY_DEVMODE'),
152 'assetPath' => \esc_url(EXTENDIFY_URL . 'public/assets'),
153 'siteId' => \esc_attr(\get_option('extendify_site_id', '')),
154 'siteCreatedAt' => \esc_attr(SiteSettings::getSiteCreatedAt()),
155 'themeSlug' => \esc_attr(\get_option('stylesheet')),
156 'version' => \esc_attr(Config::$version),
157 'siteTitle' => \esc_attr(\get_bloginfo('name')),
158 'siteProfile' => \get_option('extendify_site_profile', []),
159 'wpLanguage' => \esc_attr(\get_locale()),
160 'wpVersion' => \esc_attr(\get_bloginfo('version')),
161 'isBlockTheme' => function_exists('wp_is_block_theme') ? (bool) wp_is_block_theme() : false,
162 'userId' => \esc_attr(\get_current_user_id()),
163 'partnerLogo' => \esc_attr(PartnerData::$logo),
164 'partnerId' => \esc_attr(PartnerData::$id),
165 'partnerName' => \esc_attr(PartnerData::$name),
166 'launchDataLegacy' => \wp_json_encode((UserSelectionController::get()->get_data() ?? [])),
167 'resourceData' => \wp_json_encode((new ResourceData())->getData()),
168 'showAIConsent' => isset($partnerData['showAIConsent']) ? (bool) $partnerData['showAIConsent'] : false,
169 'showChat' => (bool) (PartnerData::setting('showChat') || constant('EXTENDIFY_DEVMODE')),
170 'useAgentOnboarding' => (bool) (
171 PartnerData::setting('useAgentOnboarding') ||
172 Config::preview('agent-onboarding') ||
173 constant('EXTENDIFY_DEVMODE')
174 ),
175 'showAIPageCreation' => (bool) (
176 PartnerData::setting('showAIPageCreation') || constant('EXTENDIFY_DEVMODE')
177 ),
178 'showAILogo' => (bool) PartnerData::setting('showAILogo'),
179 'showImprint' => array_map('esc_attr', (array) PartnerData::setting('showImprint')),
180 'consentTermsCustom' => \wp_kses((html_entity_decode(($partnerData['consentTermsCustom'] ?? ''))
181 ?? ''), $htmlAllowlist),
182 'userGaveConsent' => $userConsent ? (bool) $userConsent : false,
183 'installedPlugins' => array_map('esc_attr', array_keys(\get_plugins())),
184 'installedPluginsSlugs' => array_values(array_filter(array_map(function ($p) {
185 return $p['TextDomain'] ?? '';
186 }, \get_plugins()))),
187 'activePlugins' => array_map('esc_attr', array_values(\get_option('active_plugins', []))),
188 'frontPage' => \esc_attr(\get_option('page_on_front', 0)),
189 'globalStylesPostID' => \esc_attr(\WP_Theme_JSON_Resolver::get_user_global_styles_post_id()),
190 'showLocalizedCopy' => (bool) array_key_exists('showLocalizedCopy', $partnerData),
191 'activity' => \wp_json_encode(\get_option('extendify_shared_activity', null)),
192 'showDraft' => isset($partnerData['showDraft']) ? (bool) $partnerData['showDraft'] : false,
193 'showLaunch' => Config::$showLaunch,
194 'phpVersion' => \esc_attr(PHP_VERSION),
195 'apexDomain' => PartnerData::setting('enableApexDomain')
196 ? rawurlencode(ApexDomain::getApexDomain(\get_home_url()))
197 : null,
198 'launchCompletedAt' => \esc_attr(\get_option('extendify_onboarding_completed', false)),
199 'showSiteQuestions' => (bool) (
200 PartnerData::setting('showLaunchQuestions') || Config::preview('launch-questions')
201 ),
202 'products' => ProductsData::get(),
203 'showAIAgents' => (bool) (PartnerData::setting('showAIAgents') || Config::preview('ai-agent')),
204 'pluginGroupId' => Escaper::recursiveEscAttr(PartnerData::setting('pluginGroupId')),
205 'adminPagesMenuList' => get_option('_transient_extendify_admin_pages_menu', []),
206 'globalState' => ImageGenerationController::get()->get_data(),
207 ]),
208 'before'
209 );
210
211 \wp_set_script_translations('extendify-common', 'extendify-local', EXTENDIFY_PATH . 'languages/js');
212 \wp_set_script_translations(
213 Config::$slug . '-shared-scripts',
214 'extendify-local',
215 EXTENDIFY_PATH . 'languages/js'
216 );
217
218 \wp_enqueue_style(
219 Config::$slug . '-shared-common-styles',
220 EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-shared.css'],
221 [],
222 Config::$version,
223 'all'
224 );
225 $cssColorVars = PartnerData::cssVariableMapping();
226 $cssString = implode('; ', array_map(function ($k, $v) {
227 return "$k: $v";
228 }, array_keys($cssColorVars), $cssColorVars));
229 \wp_add_inline_style(
230 Config::$slug . '-shared-common-styles',
231 wp_strip_all_tags(":root { $cssString; }")
232 );
233 }
234
235 /**
236 * Adds additional meta fields to post types
237 *
238 * @return void
239 */
240 public function addExtraMetaFields()
241 {
242 // Add a tag to pages that were made with Launch.
243 register_post_meta('page', 'made_with_extendify_launch', [
244 'single' => true,
245 'type' => 'boolean',
246 'show_in_rest' => true,
247 ]);
248 register_post_meta('post', 'made_with_extendify_launch', [
249 'single' => true,
250 'type' => 'boolean',
251 'show_in_rest' => true,
252 ]);
253 }
254
255 /**
256 * Records plugin search terms from the WordPress plugin search page
257 * Stores terms in the 'extendify_plugin_search_terms' option
258 *
259 * @return void
260 */
261 public function recordPluginsSearchTerms()
262 {
263 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing
264 $searchTerm = isset($_POST['s']) ? \sanitize_text_field(\wp_unslash(urldecode($_POST['s']))) : '';
265 if (empty($searchTerm)) {
266 return;
267 }
268
269 $searchTerms = \get_option('extendify_plugin_search_terms', []);
270 $searchTerms[] = $searchTerm;
271 $searchTerms = array_unique($searchTerms);
272
273 \update_option('extendify_plugin_search_terms', $searchTerms);
274 }
275
276 /**
277 * Records block search terms from the WordPress the editor's block search
278 * Stores terms in the 'extendify_block_search_terms' option
279 *
280 * @return void
281 */
282 public function recordBlocksSearchTerms()
283 {
284 // Exits early if it is not a REST API request.
285 if (!\wp_is_serving_rest_request()) {
286 return;
287 }
288
289 // Exits early if it is not a GET request.
290 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'GET') {
291 return;
292 }
293
294 $wp = $GLOBALS['wp'];
295
296 $restRoute = ($wp->query_vars['rest_route'] ?? '');
297 // Exits early if it's not the blocks search route.
298 if ($restRoute !== '/wp/v2/block-directory/search') {
299 return;
300 }
301
302 $searchTerm = \sanitize_text_field(\wp_unslash(($wp->query_vars['term'] ?? '')));
303 // Exits early if the search term is empty or is not user input.
304 if (empty($searchTerm) || str_starts_with($searchTerm, 'block:')) {
305 return;
306 }
307
308 // Get current search term from the url and merge it with existing search terms.
309 $searchTerms = \get_option('extendify_block_search_terms', []);
310 \update_option('extendify_block_search_terms', array_merge($searchTerms, [$searchTerm]));
311 }
312
313 /**
314 * Updates the user meta to disable the welcome guide from the Gutenberg editor
315 * and close the pattern modal.
316 *
317 * @return void
318 */
319 public function updateUserMeta()
320 {
321 $currentPreferences = get_user_meta(get_current_user_id(), 'wp_persisted_preferences', true);
322 if (!$currentPreferences) {
323 $currentPreferences = [];
324 }
325
326 $postPreferences = array_key_exists('core/edit-post', $currentPreferences)
327 ? $currentPreferences['core/edit-post']
328 : [];
329 $corePreferences = array_key_exists('core', $currentPreferences) ? $currentPreferences['core'] : [];
330
331 $newPreferences = array_merge($currentPreferences, [
332 'core/edit-post' => array_merge($postPreferences, ['welcomeGuide' => false]),
333 'core' => array_merge($corePreferences, ['enableChoosePatternModal' => false]),
334 '_modified' => wp_date('Y-m-d\TH:i:s.v\Z'),
335 ]);
336
337 update_user_meta(get_current_user_id(), 'wp_persisted_preferences', $newPreferences);
338 }
339
340 /**
341 * Records search terms used when browsing themes in the admin interface.
342 *
343 * This method listens for the 'query-themes' AJAX action, extracts the search term
344 * from the request, and stores it in the 'extendify_theme_search_terms' option.
345 * Duplicate terms are filtered out.
346 *
347 * @return void
348 */
349 public function recordThemesSearchTerms()
350 {
351 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing
352 $searchTerm = \sanitize_text_field(\wp_unslash(urldecode(($_POST['request']['search'] ?? ''))));
353 if (empty($searchTerm)) {
354 return;
355 }
356
357 $searchTerms = \get_option('extendify_theme_search_terms', []);
358 $searchTerms[] = $searchTerm;
359 $searchTerms = array_unique($searchTerms);
360
361 \update_option('extendify_theme_search_terms', $searchTerms);
362 }
363 }
364