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 -7 1.3.25 → 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'));
@@ -348,8 +425,11 @@
348 425 }
349 426
350 427 public function renderAdminMenu()
351 428 {
429 + if (!apply_filters('fluent_cart/show_admin_top_bar', true)) {
430 + return;
431 + }
352 432 AdminHelper::getAdminMenu(true);
353 433 }
354 434
355 435 public function pushProductNav($post)
@@ -370,13 +450,22 @@
370 450
371 451 do_action('fluent_cart/loading_app', $app);
372 452
373 453 //This should be done before enqueueing the global script.
374 - 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']);
375 456
376 457 //Don't register this script using vite.
377 - 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 + }
378 465
466 + wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), ['wp-hooks'], FLUENTCART_VERSION, true);
467 +
379 468 $manager = GatewayManager::getInstance();
380 469 $payment_routes = $manager->getRoutes();
381 470 // $payment_routes = apply_filters('fluent_cart/payments/payment_method_settings_routes', []);
382 471
@@ -384,15 +473,18 @@
384 473
385 474 $settings = new StoreSettings();
386 475 $checkoutUrl = add_query_arg(Helper::INSTANT_CHECKOUT_URL_PARAM, '=', $settings->getCheckoutPage());
387 476 $restVars = Helper::getRestInfo();
477 + $hasDataMigrations = current_user_can('manage_options') && DataBackfills::hasPending();
388 478
389 479 $filterOptions = [
390 480 'order_filter_options' => OrderFilter::getTableFilterOptions(),
391 481 'customer_filter_options' => CustomerFilter::getTableFilterOptions(),
392 482 'product_filter_options' => ProductFilter::getTableFilterOptions(),
393 - 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
394 - '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(),
395 487 ];
396 488 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
397 489
398 490 // Structured table config keyed by table name (used by Table.js base class)
@@ -400,13 +492,35 @@
400 492 'order_table' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
401 493 'customers' => ['filters' => Arr::get($filterOptions, 'customer_filter_options', [])],
402 494 'product_table' => ['filters' => Arr::get($filterOptions, 'product_filter_options', [])],
403 495 'licenses' => ['filters' => Arr::get($filterOptions, 'license_filter_options', [])],
496 + 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
404 497 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
405 498 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
406 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', [])],
407 504 ];
408 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 +
409 523 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
410 524 'filterOptions' => $filterOptions
411 525 ]);
412 526
@@ -417,8 +531,9 @@
417 531 $appConfig['version'] = FLUENTCART_VERSION;
418 532 $appConfig['permissions'] = PermissionManager::getUserPermissions();
419 533 $appConfig['isProActive'] = App::isProActive();
420 534 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
535 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
421 536
422 537 $appConfig['logos'] = [
423 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
424 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -423,9 +538,9 @@
423 538 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
424 539 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
425 540 ];
426 541 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
427 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
542 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
428 543
429 544 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
430 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
431 546 'app_config' => $appConfig,
@@ -430,8 +545,9 @@
430 545 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
431 546 'app_config' => $appConfig,
432 547 'slug' => $app->config->get('app.slug'),
433 548 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
549 + 'wp_admin_url' => rtrim(admin_url(), '/'),
434 550 'frontend_url' => URL::getFrontEndUrl(''),
435 551 'nonce' => wp_create_nonce($slug),
436 552 'rest' => $restVars,
437 553 'brand_logo' => $this->getMenuIcon(),
@@ -456,9 +572,9 @@
456 572 'order_statues' => Status::getOrderStatuses(),
457 573 'editable_order_statues' => Status::getEditableOrderStatuses(),
458 574 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
459 575 'shipping_statuses' => Status::getShippingStatuses(),
460 - 'allow_bulk_payment_status_change' => true,
576 + // Attribute library powering the Advanced Variation feature.
461 577 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
462 578 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
463 579 'product_image_base_uri' => Helper::getProductImageBaseUri(),
464 580 'add_on_modules' => AddOnModule::showAddOns(),
@@ -480,17 +596,34 @@
480 596 'is_tax_enabled' => TaxModule::isTaxEnabled(),
481 597 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
482 598 'country_tax_titles' => TaxModule::taxTitleLists(),
483 599 'site_url' => site_url(),
600 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
484 601 'modules_settings' => ModuleSettings::getAllSettings(),
485 602 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
486 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,
487 606 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
488 607
489 - 'datei18' => TransStrings::dateTimeStrings(),
608 + 'datei18' => DayjsFormatter::localizedStrings(),
490 609 'el_strings' => TransStrings::elStrings(),
491 610 'wp_locale' => get_locale(),
492 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 + ],
493 626 'fct_editor_frame' => '',
494 627 ]);
495 628
496 629 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);