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 +135 -5 1.3.27 → 1.7.0 View file →
@@ -19,8 +19,10 @@
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;
@@ -26,11 +28,19 @@
26 28 use FluentCart\App\Services\Filter\LicenseFilter;
27 29 use FluentCart\App\Services\Filter\LicenseSiteFilter;
28 30 use FluentCart\App\Services\Filter\ProductFilter;
29 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;
30 38 use FluentCart\App\Modules\Integrations\AddOnModule;
39 +use FluentCart\App\Services\DateTime\DayjsFormatter;
31 40 use FluentCart\App\Services\Translations\TransStrings;
32 41 use FluentCart\App\Services\Permission\PermissionManager;
42 +use FluentCart\Database\DataBackfills;
33 43 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
34 44
35 45 class MenuHandler
36 46 {
@@ -43,8 +53,9 @@
43 53
44 54 if ($page == 'fluent-cart') {
45 55
46 56 \FluentCart\App\Events\FirstTimePluginActivation::handle();
57 + (new AdminFooter())->register();
47 58 add_action('admin_enqueue_scripts', function () {
48 59 Vite::enqueueStyle('fluent_cart_admin_app_css',
49 60 'styles/tailwind/style.css',
50 61 );
@@ -55,8 +66,14 @@
55 66
56 67 }
57 68 });
58 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 +
59 76 // Add a post display state for special WC pages.
60 77 add_filter('display_post_states', array($this, 'addDisplayPostStates'), 10, 2);
61 78
62 79 }
@@ -72,8 +89,9 @@
72 89 $targetKeys = [
73 90 'checkout_page_id' => __('Checkout Page', 'fluent-cart'),
74 91 'cart_page_id' => __('Cart Page', 'fluent-cart'),
75 92 'receipt_page_id' => __('Receipt Page', 'fluent-cart'),
93 + 'order_review_page_id' => __('Order Review Page', 'fluent-cart'),
76 94 'shop_page_id' => __('Shop Page', 'fluent-cart'),
77 95 'customer_profile_page_id' => __('Customer Profile Page', 'fluent-cart'),
78 96 ];
79 97
@@ -107,8 +125,66 @@
107 125 return $states;
108 126
109 127 }
110 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 +
111 187 public function init()
112 188 {
113 189
114 190 add_action('admin_menu', array($this, 'addAdminMenu'));
@@ -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,8 +473,9 @@
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,8 +482,9 @@
393 482 'product_filter_options' => ProductFilter::getTableFilterOptions(),
394 483 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
395 484 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
396 485 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
486 + 'review_filter_options' => ReviewFilter::getTableFilterOptions(),
397 487 ];
398 488 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
399 489
400 490 // Structured table config keyed by table name (used by Table.js base class)
@@ -406,10 +496,31 @@
406 496 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
407 497 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
408 498 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
409 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', [])],
410 504 ];
411 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 +
412 523 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
413 524 'filterOptions' => $filterOptions
414 525 ]);
415 526
@@ -420,8 +531,9 @@
420 531 $appConfig['version'] = FLUENTCART_VERSION;
421 532 $appConfig['permissions'] = PermissionManager::getUserPermissions();
422 533 $appConfig['isProActive'] = App::isProActive();
423 534 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
535 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
424 536
425 537 $appConfig['logos'] = [
426 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
427 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -426,9 +538,9 @@
426 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
427 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
428 540 ];
429 541 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
430 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
542 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
431 543
432 544 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
433 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
434 546 'app_config' => $appConfig,
@@ -433,8 +545,9 @@
433 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
434 546 'app_config' => $appConfig,
435 547 'slug' => $app->config->get('app.slug'),
436 548 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
549 + 'wp_admin_url' => rtrim(admin_url(), '/'),
437 550 'frontend_url' => URL::getFrontEndUrl(''),
438 551 'nonce' => wp_create_nonce($slug),
439 552 'rest' => $restVars,
440 553 'brand_logo' => $this->getMenuIcon(),
@@ -459,9 +572,9 @@
459 572 'order_statues' => Status::getOrderStatuses(),
460 573 'editable_order_statues' => Status::getEditableOrderStatuses(),
461 574 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
462 575 'shipping_statuses' => Status::getShippingStatuses(),
463 - 'allow_bulk_payment_status_change' => true,
576 + // Attribute library powering the Advanced Variation feature.
464 577 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
465 578 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
466 579 'product_image_base_uri' => Helper::getProductImageBaseUri(),
467 580 'add_on_modules' => AddOnModule::showAddOns(),
@@ -483,17 +596,34 @@
483 596 'is_tax_enabled' => TaxModule::isTaxEnabled(),
484 597 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
485 598 'country_tax_titles' => TaxModule::taxTitleLists(),
486 599 'site_url' => site_url(),
600 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
487 601 'modules_settings' => ModuleSettings::getAllSettings(),
488 602 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
489 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,
490 606 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
491 607
492 - 'datei18' => TransStrings::dateTimeStrings(),
608 + 'datei18' => DayjsFormatter::localizedStrings(),
493 609 'el_strings' => TransStrings::elStrings(),
494 610 'wp_locale' => get_locale(),
495 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 + ],
496 626 'fct_editor_frame' => '',
497 627 ]);
498 628
499 629 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);