| 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\Notifications\Availability; |
| 14 |
use Extendify\Shared\Controllers\UserSelectionController; |
| 15 |
use Extendify\Shared\DataProvider\NotificationData; |
| 16 |
use Extendify\Shared\DataProvider\ResourceData; |
| 17 |
use Extendify\Shared\Services\AdminMenuList; |
| 18 |
use Extendify\Shared\Services\ApexDomain\ApexDomain; |
| 19 |
use Extendify\Shared\Services\Escaper; |
| 20 |
use Extendify\Shared\Services\PluginDependencies\SimplyBook; |
| 21 |
use Extendify\Shared\Services\PluginsActivation\Imagify as ImagifyActivation; |
| 22 |
use Extendify\Shared\Services\PluginsActivation\Metricool as MetricoolActivation; |
| 23 |
use Extendify\Shared\Services\PluginsActivation\SimplyBook as SimplyBookActivation; |
| 24 |
use Extendify\Shared\Services\PluginsActivation\TranslatePress as TranslatePressActivation; |
| 25 |
use Extendify\Shared\Services\SiteImages; |
| 26 |
use Extendify\SiteSettings; |
| 27 |
use Extendify\Shared\Controllers\ImageGenerationController; |
| 28 |
use Extendify\Shared\DataProvider\ProductsData; |
| 29 |
|
| 30 |
/** |
| 31 |
* This class handles any file loading for the admin area. |
| 32 |
*/ |
| 33 |
|
| 34 |
class Admin |
| 35 |
{ |
| 36 |
/** |
| 37 |
* Adds various actions to set up the page |
| 38 |
* |
| 39 |
* @return void |
| 40 |
*/ |
| 41 |
public function __construct() |
| 42 |
{ |
| 43 |
\add_action('init', [$this, 'addExtraMetaFields']); |
| 44 |
\add_action('admin_enqueue_scripts', [$this, 'loadGlobalScripts']); |
| 45 |
\add_action('wp_enqueue_scripts', [$this, 'loadGlobalScripts']); |
| 46 |
\add_action('wp_ajax_search-install-plugins', [$this, 'recordPluginsSearchTerms'], -1); |
| 47 |
\add_action('rest_api_init', [$this, 'recordBlocksSearchTerms']); |
| 48 |
\add_action('wp_ajax_query-themes', [$this, 'recordThemesSearchTerms'], -1); |
| 49 |
AdminMenuList::init(); |
| 50 |
\add_action('simplybook_activation', [SimplyBook::class, 'getIndustryCode'], 10, 0); |
| 51 |
} |
| 52 |
|
| 53 |
// phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh |
| 54 |
/** |
| 55 |
* Adds scripts to every page |
| 56 |
* |
| 57 |
* @return void |
| 58 |
*/ |
| 59 |
public function loadGlobalScripts() |
| 60 |
{ |
| 61 |
\wp_enqueue_media(); |
| 62 |
|
| 63 |
$this->updateUserMeta(); |
| 64 |
|
| 65 |
$version = constant('EXTENDIFY_DEVMODE') ? uniqid() : Config::$version; |
| 66 |
|
| 67 |
/** |
| 68 |
* Enqueue shared JavaScript files if they exist in the asset manifest |
| 69 |
* Ensures proper loading order: vendors -> common |
| 70 |
*/ |
| 71 |
|
| 72 |
// Enqueue the vendor chunk first. |
| 73 |
if (isset(Config::$assetManifest['extendify-vendors.js'])) { |
| 74 |
wp_enqueue_script( |
| 75 |
'extendify-vendors', |
| 76 |
EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-vendors.js'], |
| 77 |
[], |
| 78 |
$version, |
| 79 |
true |
| 80 |
// Load in footer. |
| 81 |
); |
| 82 |
} |
| 83 |
|
| 84 |
// Enqueue the common chunk next, dependent on vendors. |
| 85 |
if (isset(Config::$assetManifest['extendify-common.js'])) { |
| 86 |
wp_enqueue_script( |
| 87 |
'extendify-common', |
| 88 |
EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-common.js'], |
| 89 |
['extendify-vendors'], |
| 90 |
$version, |
| 91 |
true |
| 92 |
); |
| 93 |
} |
| 94 |
|
| 95 |
/* |
| 96 |
* Loads a unique runtime generated by Webpack to manage all the other generated scripts. |
| 97 |
* Without this runtime, the other Extendify scripts won't load. |
| 98 |
*/ |
| 99 |
$scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . Config::$assetManifest['extendify-runtime.php']; |
| 100 |
$fallback = [ |
| 101 |
'dependencies' => [], |
| 102 |
'version' => $version, |
| 103 |
]; |
| 104 |
$scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback; |
| 105 |
|
| 106 |
foreach ($scriptAsset['dependencies'] as $style) { |
| 107 |
\wp_enqueue_style($style); |
| 108 |
} |
| 109 |
|
| 110 |
\wp_enqueue_script( |
| 111 |
Config::$slug . '-runtime-scripts', |
| 112 |
EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-runtime.js'], |
| 113 |
$scriptAsset['dependencies'], |
| 114 |
$scriptAsset['version'], |
| 115 |
true |
| 116 |
); |
| 117 |
|
| 118 |
$scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . Config::$assetManifest['extendify-shared.php']; |
| 119 |
$fallback = [ |
| 120 |
'dependencies' => [], |
| 121 |
'version' => $version, |
| 122 |
]; |
| 123 |
$scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback; |
| 124 |
|
| 125 |
foreach ($scriptAsset['dependencies'] as $style) { |
| 126 |
\wp_enqueue_style($style); |
| 127 |
} |
| 128 |
|
| 129 |
\wp_enqueue_script( |
| 130 |
Config::$slug . '-shared-scripts', |
| 131 |
EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-shared.js'], |
| 132 |
$scriptAsset['dependencies'], |
| 133 |
$scriptAsset['version'], |
| 134 |
true |
| 135 |
); |
| 136 |
|
| 137 |
$partnerData = PartnerData::getPartnerData(); |
| 138 |
$userConsent = get_user_meta(get_current_user_id(), 'extendify_ai_consent', true); |
| 139 |
$htmlAllowlist = [ |
| 140 |
'a' => [ |
| 141 |
'target' => [], |
| 142 |
'href' => [], |
| 143 |
'rel' => [], |
| 144 |
], |
| 145 |
]; |
| 146 |
|
| 147 |
if (!function_exists('get_plugins')) { |
| 148 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 149 |
} |
| 150 |
|
| 151 |
$activePlugins = array_values(\get_option('active_plugins', [])); |
| 152 |
$activePluginSlugs = array_map(function ($plugin) { |
| 153 |
return dirname($plugin); |
| 154 |
}, $activePlugins); |
| 155 |
$productActivationPlugins = PartnerData::setting('showProductActivation') ?? []; |
| 156 |
|
| 157 |
$productActivationPlugins = array_filter( |
| 158 |
$productActivationPlugins, |
| 159 |
function ($plugin) use ($activePluginSlugs) { |
| 160 |
return in_array($plugin['slug'], $activePluginSlugs); |
| 161 |
} |
| 162 |
); |
| 163 |
|
| 164 |
$activations = [ |
| 165 |
ImagifyActivation::class, |
| 166 |
MetricoolActivation::class, |
| 167 |
SimplyBookActivation::class, |
| 168 |
TranslatePressActivation::class, |
| 169 |
]; |
| 170 |
|
| 171 |
$productActivationPlugins = array_map(function ($plugin) use ($activations) { |
| 172 |
foreach ($activations as $activation) { |
| 173 |
if ($plugin['slug'] === $activation::slug()) { |
| 174 |
return array_merge($plugin, [ |
| 175 |
'scriptData' => $activation::scriptData(), |
| 176 |
'eligible' => $activation::isEligible(), |
| 177 |
]); |
| 178 |
} |
| 179 |
} |
| 180 |
|
| 181 |
return $plugin; |
| 182 |
}, $productActivationPlugins); |
| 183 |
|
| 184 |
// Visitors read the stamp and the alt text, so both resolve in the site's locale. |
| 185 |
$switchedLocale = \switch_to_locale(\get_locale()); |
| 186 |
// translators: Short label stamped onto an image marking it as AI-generated. |
| 187 |
// Give the all-caps form your language uses. |
| 188 |
$aiImageLabel = \_x('AI GENERATED', 'uppercase', 'extendify-local'); |
| 189 |
// translators: %s is the image description. Alt text prefix marking an image as AI-generated. |
| 190 |
$aiImageAltPattern = \__('AI Generated: %s', 'extendify-local'); |
| 191 |
if ($switchedLocale) { |
| 192 |
\restore_previous_locale(); |
| 193 |
} |
| 194 |
|
| 195 |
$extendifyCodeData = (array) PartnerData::setting('extendifyCodeData'); |
| 196 |
|
| 197 |
// esc_url() strips the {DESCRIPTION} braces; shield the placeholder across it. |
| 198 |
$extendifyCodeLink = str_replace( |
| 199 |
'__EXTENDIFY_DESCRIPTION__', |
| 200 |
'{DESCRIPTION}', |
| 201 |
\esc_url_raw(str_replace( |
| 202 |
'{DESCRIPTION}', |
| 203 |
'__EXTENDIFY_DESCRIPTION__', |
| 204 |
(string) ($extendifyCodeData['link'] ?? '') |
| 205 |
)) |
| 206 |
); |
| 207 |
|
| 208 |
\wp_add_inline_script( |
| 209 |
Config::$slug . '-shared-scripts', |
| 210 |
'window.extSharedData = ' . \wp_json_encode([ |
| 211 |
'root' => \esc_url_raw(rest_url(Config::$slug . '/' . Config::$apiVersion)), |
| 212 |
'homeUrl' => \esc_url_raw(\get_home_url()), |
| 213 |
'adminUrl' => \esc_url_raw(\admin_url()), |
| 214 |
'nonce' => \esc_attr(\wp_create_nonce('wp_rest')), |
| 215 |
'devbuild' => (bool) constant('EXTENDIFY_DEVMODE'), |
| 216 |
'assetPath' => \esc_url(EXTENDIFY_URL . 'public/assets'), |
| 217 |
'siteId' => \esc_attr(\get_option('extendify_site_id', '')), |
| 218 |
'siteCreatedAt' => \esc_attr(SiteSettings::getSiteCreatedAt()), |
| 219 |
'themeSlug' => \esc_attr(\get_option('stylesheet')), |
| 220 |
'version' => \esc_attr(Config::$version), |
| 221 |
'siteTitle' => \esc_attr(\get_bloginfo('name')), |
| 222 |
'siteProfile' => \get_option('extendify_site_profile', []), |
| 223 |
// Empty when the launch-time image fetch failed. |
| 224 |
'siteImages' => SiteImages::normalize(\get_option('extendify_site_images', [])), |
| 225 |
'wpLanguage' => \esc_attr(\get_locale()), |
| 226 |
'aiImageLabel' => $aiImageLabel, |
| 227 |
'aiImageAltPattern' => $aiImageAltPattern, |
| 228 |
'wpVersion' => \esc_attr(\get_bloginfo('version')), |
| 229 |
'isBlockTheme' => function_exists('wp_is_block_theme') ? (bool) wp_is_block_theme() : false, |
| 230 |
'userId' => \esc_attr(\get_current_user_id()), |
| 231 |
// phpcs:ignore WordPress.Security.NonceVerification |
| 232 |
'userEmail' => isset($_GET['extendify-launch-success']) |
| 233 |
? \esc_attr(\wp_get_current_user()->user_email) |
| 234 |
: null, |
| 235 |
'partnerLogo' => \esc_attr(PartnerData::$logo), |
| 236 |
'partnerId' => \esc_attr(PartnerData::$id), |
| 237 |
'partnerName' => \esc_attr(PartnerData::$name), |
| 238 |
'launchDataLegacy' => \wp_json_encode((UserSelectionController::get()->get_data() ?? [])), |
| 239 |
'resourceData' => \wp_json_encode((new ResourceData())->getData()), |
| 240 |
'notifications' => \wp_json_encode( |
| 241 |
Availability::available(NotificationData::get()) |
| 242 |
), |
| 243 |
'notificationState' => \get_user_meta( |
| 244 |
\get_current_user_id(), |
| 245 |
'extendify_notification_state', |
| 246 |
true |
| 247 |
) ?: ['cards' => []], |
| 248 |
'showAIConsent' => isset($partnerData['showAIConsent']) ? (bool) $partnerData['showAIConsent'] : false, |
| 249 |
'showChat' => (bool) (PartnerData::setting('showChat') || constant('EXTENDIFY_DEVMODE')), |
| 250 |
'useAgentOnboarding' => (bool) ( |
| 251 |
PartnerData::setting('useAgentOnboarding') || |
| 252 |
Config::preview('agent-onboarding') || |
| 253 |
constant('EXTENDIFY_DEVMODE') |
| 254 |
), |
| 255 |
'showAIPageCreation' => (bool) ( |
| 256 |
PartnerData::setting('showAIPageCreation') || constant('EXTENDIFY_DEVMODE') |
| 257 |
), |
| 258 |
'showAILogo' => (bool) PartnerData::setting('showAILogo'), |
| 259 |
'showImprint' => array_map('esc_attr', (array) PartnerData::setting('showImprint')), |
| 260 |
'showProductActivation' => array_values($productActivationPlugins), |
| 261 |
'consentTermsCustom' => \wp_kses((html_entity_decode( |
| 262 |
($partnerData['consentTermsCustom'] ?? ''), |
| 263 |
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 |
| 264 |
) ?? ''), $htmlAllowlist), |
| 265 |
'userGaveConsent' => $userConsent ? (bool) $userConsent : false, |
| 266 |
'installedPlugins' => array_map('esc_attr', array_keys(\get_plugins())), |
| 267 |
'activePlugins' => $activePlugins, |
| 268 |
'installedPluginsSlugs' => array_values(array_filter(array_map(function ($p) { |
| 269 |
return $p['TextDomain'] ?? ''; |
| 270 |
}, \get_plugins()))), |
| 271 |
'frontPage' => \esc_attr(\get_option('page_on_front', 0)), |
| 272 |
'globalStylesPostID' => \esc_attr(\WP_Theme_JSON_Resolver::get_user_global_styles_post_id()), |
| 273 |
'showLocalizedCopy' => (bool) array_key_exists('showLocalizedCopy', $partnerData), |
| 274 |
'activity' => \wp_json_encode(\get_option('extendify_shared_activity', null)), |
| 275 |
'showDraft' => isset($partnerData['showDraft']) ? (bool) $partnerData['showDraft'] : false, |
| 276 |
'showLaunch' => Config::$showLaunch, |
| 277 |
'phpVersion' => \esc_attr(PHP_VERSION), |
| 278 |
'apexDomain' => PartnerData::setting('enableApexDomain') |
| 279 |
? rawurlencode(ApexDomain::getApexDomain(\get_home_url())) |
| 280 |
: null, |
| 281 |
'launchCompletedAt' => \esc_attr(\get_option('extendify_onboarding_completed', false)), |
| 282 |
'showSiteQuestions' => (bool) ( |
| 283 |
PartnerData::setting('showLaunchQuestions') || Config::preview('launch-questions') |
| 284 |
), |
| 285 |
'products' => ProductsData::get(), |
| 286 |
'showAIAgents' => (bool) (PartnerData::setting('showAIAgents') || Config::preview('ai-agent')), |
| 287 |
'showExtendifyCode' => (bool) PartnerData::setting('showExtendifyCode'), |
| 288 |
'extendifyCodeData' => [ |
| 289 |
'link' => $extendifyCodeLink, |
| 290 |
'title' => $extendifyCodeData['title'] ?? '', |
| 291 |
'message' => $extendifyCodeData['message'] ?? '', |
| 292 |
'ctaPrimary' => $extendifyCodeData['cta-primary'] ?? '', |
| 293 |
], |
| 294 |
'pluginGroupId' => Escaper::recursiveEscAttr(PartnerData::setting('pluginGroupId')), |
| 295 |
'adminPagesMenuList' => get_option('_transient_extendify_admin_pages_menu', []), |
| 296 |
'globalState' => ImageGenerationController::get()->get_data(), |
| 297 |
]), |
| 298 |
'before' |
| 299 |
); |
| 300 |
|
| 301 |
\wp_set_script_translations('extendify-common', 'extendify-local', EXTENDIFY_PATH . 'languages/js'); |
| 302 |
\wp_set_script_translations( |
| 303 |
Config::$slug . '-shared-scripts', |
| 304 |
'extendify-local', |
| 305 |
EXTENDIFY_PATH . 'languages/js' |
| 306 |
); |
| 307 |
|
| 308 |
\wp_enqueue_style( |
| 309 |
Config::$slug . '-shared-common-styles', |
| 310 |
EXTENDIFY_BASE_URL . 'public/build/' . Config::$assetManifest['extendify-shared.css'], |
| 311 |
[], |
| 312 |
Config::$version, |
| 313 |
'all' |
| 314 |
); |
| 315 |
$cssColorVars = PartnerData::cssVariableMapping(); |
| 316 |
$cssString = implode('; ', array_map(function ($k, $v) { |
| 317 |
return "$k: $v"; |
| 318 |
}, array_keys($cssColorVars), $cssColorVars)); |
| 319 |
\wp_add_inline_style( |
| 320 |
Config::$slug . '-shared-common-styles', |
| 321 |
wp_strip_all_tags(":root { $cssString; }") |
| 322 |
); |
| 323 |
} |
| 324 |
|
| 325 |
/** |
| 326 |
* Adds additional meta fields to post types |
| 327 |
* |
| 328 |
* @return void |
| 329 |
*/ |
| 330 |
public function addExtraMetaFields() |
| 331 |
{ |
| 332 |
// Add a tag to pages that were made with Launch. |
| 333 |
register_post_meta('page', 'made_with_extendify_launch', [ |
| 334 |
'single' => true, |
| 335 |
'type' => 'boolean', |
| 336 |
'show_in_rest' => true, |
| 337 |
]); |
| 338 |
register_post_meta('post', 'made_with_extendify_launch', [ |
| 339 |
'single' => true, |
| 340 |
'type' => 'boolean', |
| 341 |
'show_in_rest' => true, |
| 342 |
]); |
| 343 |
|
| 344 |
// Marks AI-generated images for EU AI Act disclosure. |
| 345 |
register_post_meta('attachment', 'extendify_ai_generated', [ |
| 346 |
'single' => true, |
| 347 |
'type' => 'boolean', |
| 348 |
'show_in_rest' => true, |
| 349 |
]); |
| 350 |
} |
| 351 |
|
| 352 |
/** |
| 353 |
* Records plugin search terms from the WordPress plugin search page |
| 354 |
* Stores terms in the 'extendify_plugin_search_terms' option |
| 355 |
* |
| 356 |
* @return void |
| 357 |
*/ |
| 358 |
public function recordPluginsSearchTerms() |
| 359 |
{ |
| 360 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing |
| 361 |
$searchTerm = isset($_POST['s']) ? \sanitize_text_field(\wp_unslash(urldecode($_POST['s']))) : ''; |
| 362 |
if (empty($searchTerm)) { |
| 363 |
return; |
| 364 |
} |
| 365 |
|
| 366 |
$searchTerms = \get_option('extendify_plugin_search_terms', []); |
| 367 |
$searchTerms[] = $searchTerm; |
| 368 |
$searchTerms = array_unique($searchTerms); |
| 369 |
|
| 370 |
\update_option('extendify_plugin_search_terms', $searchTerms); |
| 371 |
} |
| 372 |
|
| 373 |
/** |
| 374 |
* Records block search terms from the WordPress the editor's block search |
| 375 |
* Stores terms in the 'extendify_block_search_terms' option |
| 376 |
* |
| 377 |
* @return void |
| 378 |
*/ |
| 379 |
public function recordBlocksSearchTerms() |
| 380 |
{ |
| 381 |
// Exits early if it is not a REST API request. |
| 382 |
if (!\wp_is_serving_rest_request()) { |
| 383 |
return; |
| 384 |
} |
| 385 |
|
| 386 |
// Exits early if it is not a GET request. |
| 387 |
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'GET') { |
| 388 |
return; |
| 389 |
} |
| 390 |
|
| 391 |
$wp = $GLOBALS['wp']; |
| 392 |
|
| 393 |
$restRoute = ($wp->query_vars['rest_route'] ?? ''); |
| 394 |
// Exits early if it's not the blocks search route. |
| 395 |
if ($restRoute !== '/wp/v2/block-directory/search') { |
| 396 |
return; |
| 397 |
} |
| 398 |
|
| 399 |
$searchTerm = \sanitize_text_field(\wp_unslash(($wp->query_vars['term'] ?? ''))); |
| 400 |
// Exits early if the search term is empty or is not user input. |
| 401 |
if (empty($searchTerm) || str_starts_with($searchTerm, 'block:')) { |
| 402 |
return; |
| 403 |
} |
| 404 |
|
| 405 |
// Get current search term from the url and merge it with existing search terms. |
| 406 |
$searchTerms = \get_option('extendify_block_search_terms', []); |
| 407 |
\update_option('extendify_block_search_terms', array_merge($searchTerms, [$searchTerm])); |
| 408 |
} |
| 409 |
|
| 410 |
/** |
| 411 |
* Updates the user meta to disable the welcome guide from the Gutenberg editor |
| 412 |
* and close the pattern modal. |
| 413 |
* |
| 414 |
* @return void |
| 415 |
*/ |
| 416 |
public function updateUserMeta() |
| 417 |
{ |
| 418 |
$currentPreferences = get_user_meta(get_current_user_id(), 'wp_persisted_preferences', true); |
| 419 |
if (!$currentPreferences) { |
| 420 |
$currentPreferences = []; |
| 421 |
} |
| 422 |
|
| 423 |
$postPreferences = array_key_exists('core/edit-post', $currentPreferences) |
| 424 |
? $currentPreferences['core/edit-post'] |
| 425 |
: []; |
| 426 |
$corePreferences = array_key_exists('core', $currentPreferences) ? $currentPreferences['core'] : []; |
| 427 |
|
| 428 |
$newPreferences = array_merge($currentPreferences, [ |
| 429 |
'core/edit-post' => array_merge($postPreferences, ['welcomeGuide' => false]), |
| 430 |
'core' => array_merge($corePreferences, ['enableChoosePatternModal' => false]), |
| 431 |
'_modified' => wp_date('Y-m-d\TH:i:s.v\Z'), |
| 432 |
]); |
| 433 |
|
| 434 |
update_user_meta(get_current_user_id(), 'wp_persisted_preferences', $newPreferences); |
| 435 |
} |
| 436 |
|
| 437 |
/** |
| 438 |
* Records search terms used when browsing themes in the admin interface. |
| 439 |
* |
| 440 |
* This method listens for the 'query-themes' AJAX action, extracts the search term |
| 441 |
* from the request, and stores it in the 'extendify_theme_search_terms' option. |
| 442 |
* Duplicate terms are filtered out. |
| 443 |
* |
| 444 |
* @return void |
| 445 |
*/ |
| 446 |
public function recordThemesSearchTerms() |
| 447 |
{ |
| 448 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing |
| 449 |
$searchTerm = \sanitize_text_field(\wp_unslash(urldecode(($_POST['request']['search'] ?? '')))); |
| 450 |
if (empty($searchTerm)) { |
| 451 |
return; |
| 452 |
} |
| 453 |
|
| 454 |
$searchTerms = \get_option('extendify_theme_search_terms', []); |
| 455 |
$searchTerms[] = $searchTerm; |
| 456 |
$searchTerms = array_unique($searchTerms); |
| 457 |
|
| 458 |
\update_option('extendify_theme_search_terms', $searchTerms); |
| 459 |
} |
| 460 |
} |
| 461 |
|