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