PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 All 50 releases
← All changes | app/Hooks/Handlers/MenuHandler.php +140 -8 1.3.20 → 1.7.0 View file →
@@ -19,17 +19,28 @@
19 19 use FluentCart\App\Models\AttributeTerm;
20 20 use FluentCart\App\Models\AttributeGroup;
21 21 use FluentCart\App\Models\ShippingMethod;
22 22 use FluentCart\App\Modules\Tax\TaxModule;
23 +use FluentCart\App\Modules\FluentPlayer\FluentPlayerBridge;
24 +use FluentCart\App\Modules\AdminFooter\AdminFooter;
23 25 use FluentCart\App\Helpers\CurrenciesHelper;
24 26 use FluentCart\App\Services\Filter\TaxFilter;
25 27 use FluentCart\App\Services\Filter\OrderFilter;
26 28 use FluentCart\App\Services\Filter\LicenseFilter;
29 +use FluentCart\App\Services\Filter\LicenseSiteFilter;
27 30 use FluentCart\App\Services\Filter\ProductFilter;
28 31 use FluentCart\App\Services\Filter\CustomerFilter;
32 +use FluentCart\App\Services\Filter\CouponFilter;
33 +use FluentCart\App\Services\Filter\LogFilter;
34 +use FluentCart\App\Services\Filter\OrderBumpFilter;
35 +use FluentCart\App\Services\Filter\ReviewFilter;
36 +use FluentCart\App\Modules\Shipping\Services\Filter\ShippingClassFilter;
37 +use FluentCart\App\Modules\Shipping\Services\Filter\ShippingZoneFilter;
29 38 use FluentCart\App\Modules\Integrations\AddOnModule;
39 +use FluentCart\App\Services\DateTime\DayjsFormatter;
30 40 use FluentCart\App\Services\Translations\TransStrings;
31 41 use FluentCart\App\Services\Permission\PermissionManager;
42 +use FluentCart\Database\DataBackfills;
32 43 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
33 44
34 45 class MenuHandler
35 46 {
@@ -42,8 +53,9 @@
42 53
43 54 if ($page == 'fluent-cart') {
44 55
45 56 \FluentCart\App\Events\FirstTimePluginActivation::handle();
57 + (new AdminFooter())->register();
46 58 add_action('admin_enqueue_scripts', function () {
47 59 Vite::enqueueStyle('fluent_cart_admin_app_css',
48 60 'styles/tailwind/style.css',
49 61 );
@@ -54,8 +66,14 @@
54 66
55 67 }
56 68 });
57 69
70 + // Inline dark mode init script for all FluentCart admin pages (prevents flicker)
71 + add_action('admin_head', [$this, 'outputFluentCartDarkModeScript']);
72 +
73 + // Enqueue dark mode toggle JS for taxonomy pages
74 + add_action('admin_head', [$this, 'enqueueDarkModeToggleForTaxonomy']);
75 +
58 76 // Add a post display state for special WC pages.
59 77 add_filter('display_post_states', array($this, 'addDisplayPostStates'), 10, 2);
60 78
61 79 }
@@ -71,8 +89,9 @@
71 89 $targetKeys = [
72 90 'checkout_page_id' => __('Checkout Page', 'fluent-cart'),
73 91 'cart_page_id' => __('Cart Page', 'fluent-cart'),
74 92 'receipt_page_id' => __('Receipt Page', 'fluent-cart'),
93 + 'order_review_page_id' => __('Order Review Page', 'fluent-cart'),
75 94 'shop_page_id' => __('Shop Page', 'fluent-cart'),
76 95 'customer_profile_page_id' => __('Customer Profile Page', 'fluent-cart'),
77 96 ];
78 97
@@ -106,8 +125,66 @@
106 125 return $states;
107 126
108 127 }
109 128
129 + public function outputFluentCartDarkModeScript()
130 + {
131 + global $pagenow;
132 + $page = $_GET['page'] ?? '';
133 + $postType = $_GET['post_type'] ?? '';
134 +
135 + // post.php?post=ID&action=edit omits post_type; resolve it from the post ID.
136 + if ($pagenow === 'post.php' && empty($postType)) {
137 + $postId = (int) App::request()->get('post', 0);
138 + $postType = $postId ? (string) get_post_type($postId) : '';
139 + }
140 +
141 + $isFluentCartPage = $page === 'fluent-cart';
142 + $isTaxonomyList = $pagenow === 'edit-tags.php' && $postType === FluentProducts::CPT_NAME;
143 + $isTermEditPage = $pagenow === 'term.php' && $postType === FluentProducts::CPT_NAME;
144 + $isProductEditPage = ($pagenow === 'post.php' || $pagenow === 'edit.php') && $postType === FluentProducts::CPT_NAME;
145 +
146 + if (!$isFluentCartPage && !$isTaxonomyList && !$isTermEditPage && !$isProductEditPage) {
147 + return;
148 + }
149 +
150 + // Runs synchronously in <head> before any paint — applies the dark class and
151 + // data-fct-theme attribute to <html> so logos and the theme icon render correctly
152 + // from the very first frame, with no light-to-dark flash.
153 + echo '<script>' . $this->getDarkModeInitScript() . '</script>';
154 + }
155 +
156 + public function enqueueDarkModeToggleForTaxonomy()
157 + {
158 + global $pagenow;
159 + $postType = $_GET['post_type'] ?? '';
160 +
161 + $isTaxonomyList = $pagenow === 'edit-tags.php' && $postType === FluentProducts::CPT_NAME;
162 + $isTermEditPage = $pagenow === 'term.php' && $postType === FluentProducts::CPT_NAME;
163 +
164 + if ($isTaxonomyList || $isTermEditPage) {
165 + Vite::enqueueScript(
166 + 'fluent_cart_dark_mode_toggle',
167 + 'admin/dark-mode-toggle.js',
168 + [],
169 + FLUENTCART_VERSION,
170 + true
171 + );
172 + }
173 + }
174 +
175 + private function getDarkModeInitScript(): string
176 + {
177 + return "(function(){" .
178 + "var k='fluent_theme_mode',c='fluent_theme_dark'," .
179 + "s=localStorage.getItem(k)||localStorage.getItem('fcart_admin_theme')," .
180 + "t=s==='dark'?'dark':s==='light'?'light':'system'," .
181 + "d=s==='dark'||s==='system:dark'||((!s||s==='system')&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);" .
182 + "document.documentElement.setAttribute('data-fct-theme',t);" .
183 + "if(d)document.documentElement.classList.add(c);" .
184 + "})();";
185 + }
186 +
110 187 public function init()
111 188 {
112 189
113 190 add_action('admin_menu', array($this, 'addAdminMenu'));
@@ -206,9 +283,8 @@
206 283 'fluent_cart_products'
207 284 );
208 285 }
209 286
210 -
211 287 if (PermissionManager::hasPermission(['subscriptions/view'])) {
212 288 $submenu['fluent-cart']['subscriptions'] = array(
213 289 __('Subscriptions', 'fluent-cart'),
214 290 $capability,
@@ -349,8 +425,11 @@
349 425 }
350 426
351 427 public function renderAdminMenu()
352 428 {
429 + if (!apply_filters('fluent_cart/show_admin_top_bar', true)) {
430 + return;
431 + }
353 432 AdminHelper::getAdminMenu(true);
354 433 }
355 434
356 435 public function pushProductNav($post)
@@ -371,13 +450,22 @@
371 450
372 451 do_action('fluent_cart/loading_app', $app);
373 452
374 453 //This should be done before enqueueing the global script.
375 - Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks']);
454 + // wp-i18n backs the admin translator (resources/admin/utils/translator/Translator.js).
455 + Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks', 'wp-i18n']);
376 456
377 457 //Don't register this script using vite.
378 - wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), [], FLUENTCART_VERSION, true);
458 + if (!wp_script_is('wp-hooks', 'registered')) {
459 + $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
460 + wp_register_script('wp-hooks', includes_url('js/dist/hooks' . $suffix . '.js'), ['wp-polyfill'], FLUENTCART_VERSION, true);
461 + }
462 + if (!wp_script_is('wp-hooks', 'enqueued')) {
463 + wp_enqueue_script('wp-hooks');
464 + }
379 465
466 + wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), ['wp-hooks'], FLUENTCART_VERSION, true);
467 +
380 468 $manager = GatewayManager::getInstance();
381 469 $payment_routes = $manager->getRoutes();
382 470 // $payment_routes = apply_filters('fluent_cart/payments/payment_method_settings_routes', []);
383 471
@@ -385,15 +473,18 @@
385 473
386 474 $settings = new StoreSettings();
387 475 $checkoutUrl = add_query_arg(Helper::INSTANT_CHECKOUT_URL_PARAM, '=', $settings->getCheckoutPage());
388 476 $restVars = Helper::getRestInfo();
477 + $hasDataMigrations = current_user_can('manage_options') && DataBackfills::hasPending();
389 478
390 479 $filterOptions = [
391 480 'order_filter_options' => OrderFilter::getTableFilterOptions(),
392 481 'customer_filter_options' => CustomerFilter::getTableFilterOptions(),
393 482 'product_filter_options' => ProductFilter::getTableFilterOptions(),
394 - 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
395 - 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
483 + 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
484 + 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
485 + 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
486 + 'review_filter_options' => ReviewFilter::getTableFilterOptions(),
396 487 ];
397 488 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
398 489
399 490 // Structured table config keyed by table name (used by Table.js base class)
@@ -401,13 +492,35 @@
401 492 'order_table' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
402 493 'customers' => ['filters' => Arr::get($filterOptions, 'customer_filter_options', [])],
403 494 'product_table' => ['filters' => Arr::get($filterOptions, 'product_filter_options', [])],
404 495 'licenses' => ['filters' => Arr::get($filterOptions, 'license_filter_options', [])],
496 + 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
405 497 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
406 498 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
407 499 'shipping_zone_table' => ['filters' => Arr::get($filterOptions, 'shipping_zone_filter_options', [])],
500 + 'review_table' => ['filters' => Arr::get($filterOptions, 'review_filter_options', [])],
501 + // The Order Sources report filters orders, so its advanced-filter UI
502 + // reuses the Orders filter vocabulary rather than defining its own.
503 + 'source_report' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
408 504 ];
409 505
506 + // Tables that declare sort options on their filter class but carry no
507 + // filter-options entry above: the Sort popover still needs them, and
508 + // they are what `fluent_cart/{filterName}_table_sorts` feeds.
509 + $sortOnlyTables = [
510 + 'coupon_table' => CouponFilter::class,
511 + 'log_table' => LogFilter::class,
512 + 'order_bump_table' => OrderBumpFilter::class,
513 + 'shipping_class_table' => ShippingClassFilter::class,
514 + 'shipping_zone_table' => ShippingZoneFilter::class,
515 + ];
516 +
517 + foreach ($sortOnlyTables as $tableName => $filterClass) {
518 + if (empty($tableConfig[$tableName]['filters']['sorts'])) {
519 + $tableConfig[$tableName]['filters']['sorts'] = call_user_func([$filterClass, 'getSortOptions']);
520 + }
521 + }
522 +
410 523 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
411 524 'filterOptions' => $filterOptions
412 525 ]);
413 526
@@ -418,8 +531,9 @@
418 531 $appConfig['version'] = FLUENTCART_VERSION;
419 532 $appConfig['permissions'] = PermissionManager::getUserPermissions();
420 533 $appConfig['isProActive'] = App::isProActive();
421 534 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
535 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
422 536
423 537 $appConfig['logos'] = [
424 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -424,9 +538,9 @@
424 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
426 540 ];
427 541 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
428 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
542 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
429 543
430 544 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
431 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 546 'app_config' => $appConfig,
@@ -431,8 +545,9 @@
431 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 546 'app_config' => $appConfig,
433 547 'slug' => $app->config->get('app.slug'),
434 548 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
549 + 'wp_admin_url' => rtrim(admin_url(), '/'),
435 550 'frontend_url' => URL::getFrontEndUrl(''),
436 551 'nonce' => wp_create_nonce($slug),
437 552 'rest' => $restVars,
438 553 'brand_logo' => $this->getMenuIcon(),
@@ -457,9 +572,9 @@
457 572 'order_statues' => Status::getOrderStatuses(),
458 573 'editable_order_statues' => Status::getEditableOrderStatuses(),
459 574 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
460 575 'shipping_statuses' => Status::getShippingStatuses(),
461 - 'allow_bulk_payment_status_change' => true,
576 + // Attribute library powering the Advanced Variation feature.
462 577 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
463 578 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
464 579 'product_image_base_uri' => Helper::getProductImageBaseUri(),
465 580 'add_on_modules' => AddOnModule::showAddOns(),
@@ -481,17 +596,34 @@
481 596 'is_tax_enabled' => TaxModule::isTaxEnabled(),
482 597 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
483 598 'country_tax_titles' => TaxModule::taxTitleLists(),
484 599 'site_url' => site_url(),
600 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
485 601 'modules_settings' => ModuleSettings::getAllSettings(),
486 602 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
487 603 'admin_notices' => apply_filters('fluent_cart/admin_notices', []),
604 + // POSTs data-backfills/run until done; endpoint requires manage_options
605 + 'has_data_migrations' => $hasDataMigrations,
488 606 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
489 607
490 - 'datei18' => TransStrings::dateTimeStrings(),
608 + 'datei18' => DayjsFormatter::localizedStrings(),
491 609 'el_strings' => TransStrings::elStrings(),
492 610 'wp_locale' => get_locale(),
493 611 'is_full_name_required' => CheckoutFieldsSchema::isFullNameRequired(),
612 + 'business_details' => [
613 + 'company_name' => [
614 + 'enabled' => CheckoutFieldsSchema::isCompanyNameEnabled(),
615 + 'required' => CheckoutFieldsSchema::isCompanyNameRequired(),
616 + ],
617 + 'vat_number' => [
618 + 'enabled' => CheckoutFieldsSchema::isVatNumberEnabled(),
619 + 'required' => CheckoutFieldsSchema::isVatNumberRequired(),
620 + ],
621 + 'legal_registration_id' => [
622 + 'enabled' => CheckoutFieldsSchema::isLegalRegistrationIdEnabled(),
623 + 'required' => CheckoutFieldsSchema::isLegalRegistrationIdRequired(),
624 + ],
625 + ],
494 626 'fct_editor_frame' => '',
495 627 ]);
496 628
497 629 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);