PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 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 All 51 releases
← All changes | app/Hooks/Handlers/MenuHandler.php +154 -7 1.3.26 → 1.7.1 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,13 +125,85 @@
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'));
114 191
192 + // The "↳" children under Products stay visible on every page so the
193 + // sidebar never changes shape. Pro's "↳ Inventory" still hides itself
194 + // with inline CSS (it relied on the admin app to reveal it), so keep
195 + // that row visible whichever Pro version is installed.
196 + add_action('admin_enqueue_scripts', function () {
197 + wp_register_style('fluent-cart-admin-submenu', false);
198 + wp_enqueue_style('fluent-cart-admin-submenu');
199 + wp_add_inline_style('fluent-cart-admin-submenu', '
200 + #adminmenu .toplevel_page_fluent-cart li.fluent_cart_inventory {
201 + display: list-item;
202 + }
203 + ');
204 + });
205 +
115 206 /*
116 207 * Disable Gutenberg for Products
117 208 */
118 209 add_filter('parent_file', function ($parent_file) {
@@ -348,8 +439,11 @@
348 439 }
349 440
350 441 public function renderAdminMenu()
351 442 {
443 + if (!apply_filters('fluent_cart/show_admin_top_bar', true)) {
444 + return;
445 + }
352 446 AdminHelper::getAdminMenu(true);
353 447 }
354 448
355 449 public function pushProductNav($post)
@@ -370,13 +464,22 @@
370 464
371 465 do_action('fluent_cart/loading_app', $app);
372 466
373 467 //This should be done before enqueueing the global script.
374 - Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks']);
468 + // wp-i18n backs the admin translator (resources/admin/utils/translator/Translator.js).
469 + Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks', 'wp-i18n']);
375 470
376 471 //Don't register this script using vite.
377 - wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), [], FLUENTCART_VERSION, true);
472 + if (!wp_script_is('wp-hooks', 'registered')) {
473 + $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
474 + wp_register_script('wp-hooks', includes_url('js/dist/hooks' . $suffix . '.js'), ['wp-polyfill'], FLUENTCART_VERSION, true);
475 + }
476 + if (!wp_script_is('wp-hooks', 'enqueued')) {
477 + wp_enqueue_script('wp-hooks');
478 + }
378 479
480 + wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), ['wp-hooks'], FLUENTCART_VERSION, true);
481 +
379 482 $manager = GatewayManager::getInstance();
380 483 $payment_routes = $manager->getRoutes();
381 484 // $payment_routes = apply_filters('fluent_cart/payments/payment_method_settings_routes', []);
382 485
@@ -384,15 +487,18 @@
384 487
385 488 $settings = new StoreSettings();
386 489 $checkoutUrl = add_query_arg(Helper::INSTANT_CHECKOUT_URL_PARAM, '=', $settings->getCheckoutPage());
387 490 $restVars = Helper::getRestInfo();
491 + $hasDataMigrations = current_user_can('manage_options') && DataBackfills::hasPending();
388 492
389 493 $filterOptions = [
390 494 'order_filter_options' => OrderFilter::getTableFilterOptions(),
391 495 'customer_filter_options' => CustomerFilter::getTableFilterOptions(),
392 496 'product_filter_options' => ProductFilter::getTableFilterOptions(),
393 - 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
394 - 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
497 + 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
498 + 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
499 + 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
500 + 'review_filter_options' => ReviewFilter::getTableFilterOptions(),
395 501 ];
396 502 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
397 503
398 504 // Structured table config keyed by table name (used by Table.js base class)
@@ -400,13 +506,35 @@
400 506 'order_table' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
401 507 'customers' => ['filters' => Arr::get($filterOptions, 'customer_filter_options', [])],
402 508 'product_table' => ['filters' => Arr::get($filterOptions, 'product_filter_options', [])],
403 509 'licenses' => ['filters' => Arr::get($filterOptions, 'license_filter_options', [])],
510 + 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
404 511 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
405 512 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
406 513 'shipping_zone_table' => ['filters' => Arr::get($filterOptions, 'shipping_zone_filter_options', [])],
514 + 'review_table' => ['filters' => Arr::get($filterOptions, 'review_filter_options', [])],
515 + // The Order Sources report filters orders, so its advanced-filter UI
516 + // reuses the Orders filter vocabulary rather than defining its own.
517 + 'source_report' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
407 518 ];
408 519
520 + // Tables that declare sort options on their filter class but carry no
521 + // filter-options entry above: the Sort popover still needs them, and
522 + // they are what `fluent_cart/{filterName}_table_sorts` feeds.
523 + $sortOnlyTables = [
524 + 'coupon_table' => CouponFilter::class,
525 + 'log_table' => LogFilter::class,
526 + 'order_bump_table' => OrderBumpFilter::class,
527 + 'shipping_class_table' => ShippingClassFilter::class,
528 + 'shipping_zone_table' => ShippingZoneFilter::class,
529 + ];
530 +
531 + foreach ($sortOnlyTables as $tableName => $filterClass) {
532 + if (empty($tableConfig[$tableName]['filters']['sorts'])) {
533 + $tableConfig[$tableName]['filters']['sorts'] = call_user_func([$filterClass, 'getSortOptions']);
534 + }
535 + }
536 +
409 537 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
410 538 'filterOptions' => $filterOptions
411 539 ]);
412 540
@@ -417,8 +545,9 @@
417 545 $appConfig['version'] = FLUENTCART_VERSION;
418 546 $appConfig['permissions'] = PermissionManager::getUserPermissions();
419 547 $appConfig['isProActive'] = App::isProActive();
420 548 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
549 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
421 550
422 551 $appConfig['logos'] = [
423 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
424 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -423,9 +552,9 @@
423 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
424 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
425 554 ];
426 555 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
427 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
556 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
428 557
429 558 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
430 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
431 560 'app_config' => $appConfig,
@@ -430,8 +559,9 @@
430 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
431 560 'app_config' => $appConfig,
432 561 'slug' => $app->config->get('app.slug'),
433 562 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
563 + 'wp_admin_url' => rtrim(admin_url(), '/'),
434 564 'frontend_url' => URL::getFrontEndUrl(''),
435 565 'nonce' => wp_create_nonce($slug),
436 566 'rest' => $restVars,
437 567 'brand_logo' => $this->getMenuIcon(),
@@ -456,9 +586,9 @@
456 586 'order_statues' => Status::getOrderStatuses(),
457 587 'editable_order_statues' => Status::getEditableOrderStatuses(),
458 588 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
459 589 'shipping_statuses' => Status::getShippingStatuses(),
460 - 'allow_bulk_payment_status_change' => true,
590 + // Attribute library powering the Advanced Variation feature.
461 591 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
462 592 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
463 593 'product_image_base_uri' => Helper::getProductImageBaseUri(),
464 594 'add_on_modules' => AddOnModule::showAddOns(),
@@ -480,17 +610,34 @@
480 610 'is_tax_enabled' => TaxModule::isTaxEnabled(),
481 611 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
482 612 'country_tax_titles' => TaxModule::taxTitleLists(),
483 613 'site_url' => site_url(),
614 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
484 615 'modules_settings' => ModuleSettings::getAllSettings(),
485 616 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
486 617 'admin_notices' => apply_filters('fluent_cart/admin_notices', []),
618 + // POSTs data-backfills/run until done; endpoint requires manage_options
619 + 'has_data_migrations' => $hasDataMigrations,
487 620 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
488 621
489 - 'datei18' => TransStrings::dateTimeStrings(),
622 + 'datei18' => DayjsFormatter::localizedStrings(),
490 623 'el_strings' => TransStrings::elStrings(),
491 624 'wp_locale' => get_locale(),
492 625 'is_full_name_required' => CheckoutFieldsSchema::isFullNameRequired(),
626 + 'business_details' => [
627 + 'company_name' => [
628 + 'enabled' => CheckoutFieldsSchema::isCompanyNameEnabled(),
629 + 'required' => CheckoutFieldsSchema::isCompanyNameRequired(),
630 + ],
631 + 'vat_number' => [
632 + 'enabled' => CheckoutFieldsSchema::isVatNumberEnabled(),
633 + 'required' => CheckoutFieldsSchema::isVatNumberRequired(),
634 + ],
635 + 'legal_registration_id' => [
636 + 'enabled' => CheckoutFieldsSchema::isLegalRegistrationIdEnabled(),
637 + 'required' => CheckoutFieldsSchema::isLegalRegistrationIdRequired(),
638 + ],
639 + ],
493 640 'fct_editor_frame' => '',
494 641 ]);
495 642
496 643 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);