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 +149 -5 1.3.27 → 1.7.1 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,13 +125,85 @@
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'));
115 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 +
116 206 /*
117 207 * Disable Gutenberg for Products
118 208 */
119 209 add_filter('parent_file', function ($parent_file) {
@@ -349,8 +439,11 @@
349 439 }
350 440
351 441 public function renderAdminMenu()
352 442 {
443 + if (!apply_filters('fluent_cart/show_admin_top_bar', true)) {
444 + return;
445 + }
353 446 AdminHelper::getAdminMenu(true);
354 447 }
355 448
356 449 public function pushProductNav($post)
@@ -371,13 +464,22 @@
371 464
372 465 do_action('fluent_cart/loading_app', $app);
373 466
374 467 //This should be done before enqueueing the global script.
375 - 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']);
376 470
377 471 //Don't register this script using vite.
378 - 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 + }
379 479
480 + wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), ['wp-hooks'], FLUENTCART_VERSION, true);
481 +
380 482 $manager = GatewayManager::getInstance();
381 483 $payment_routes = $manager->getRoutes();
382 484 // $payment_routes = apply_filters('fluent_cart/payments/payment_method_settings_routes', []);
383 485
@@ -385,8 +487,9 @@
385 487
386 488 $settings = new StoreSettings();
387 489 $checkoutUrl = add_query_arg(Helper::INSTANT_CHECKOUT_URL_PARAM, '=', $settings->getCheckoutPage());
388 490 $restVars = Helper::getRestInfo();
491 + $hasDataMigrations = current_user_can('manage_options') && DataBackfills::hasPending();
389 492
390 493 $filterOptions = [
391 494 'order_filter_options' => OrderFilter::getTableFilterOptions(),
392 495 'customer_filter_options' => CustomerFilter::getTableFilterOptions(),
@@ -393,8 +496,9 @@
393 496 'product_filter_options' => ProductFilter::getTableFilterOptions(),
394 497 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
395 498 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
396 499 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
500 + 'review_filter_options' => ReviewFilter::getTableFilterOptions(),
397 501 ];
398 502 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
399 503
400 504 // Structured table config keyed by table name (used by Table.js base class)
@@ -406,10 +510,31 @@
406 510 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
407 511 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
408 512 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
409 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', [])],
410 518 ];
411 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 +
412 537 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
413 538 'filterOptions' => $filterOptions
414 539 ]);
415 540
@@ -420,8 +545,9 @@
420 545 $appConfig['version'] = FLUENTCART_VERSION;
421 546 $appConfig['permissions'] = PermissionManager::getUserPermissions();
422 547 $appConfig['isProActive'] = App::isProActive();
423 548 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
549 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
424 550
425 551 $appConfig['logos'] = [
426 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
427 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -426,9 +552,9 @@
426 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
427 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
428 554 ];
429 555 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
430 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
556 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
431 557
432 558 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
433 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
434 560 'app_config' => $appConfig,
@@ -433,8 +559,9 @@
433 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
434 560 'app_config' => $appConfig,
435 561 'slug' => $app->config->get('app.slug'),
436 562 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
563 + 'wp_admin_url' => rtrim(admin_url(), '/'),
437 564 'frontend_url' => URL::getFrontEndUrl(''),
438 565 'nonce' => wp_create_nonce($slug),
439 566 'rest' => $restVars,
440 567 'brand_logo' => $this->getMenuIcon(),
@@ -459,9 +586,9 @@
459 586 'order_statues' => Status::getOrderStatuses(),
460 587 'editable_order_statues' => Status::getEditableOrderStatuses(),
461 588 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
462 589 'shipping_statuses' => Status::getShippingStatuses(),
463 - 'allow_bulk_payment_status_change' => true,
590 + // Attribute library powering the Advanced Variation feature.
464 591 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
465 592 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
466 593 'product_image_base_uri' => Helper::getProductImageBaseUri(),
467 594 'add_on_modules' => AddOnModule::showAddOns(),
@@ -483,17 +610,34 @@
483 610 'is_tax_enabled' => TaxModule::isTaxEnabled(),
484 611 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
485 612 'country_tax_titles' => TaxModule::taxTitleLists(),
486 613 'site_url' => site_url(),
614 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
487 615 'modules_settings' => ModuleSettings::getAllSettings(),
488 616 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
489 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,
490 620 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
491 621
492 - 'datei18' => TransStrings::dateTimeStrings(),
622 + 'datei18' => DayjsFormatter::localizedStrings(),
493 623 'el_strings' => TransStrings::elStrings(),
494 624 'wp_locale' => get_locale(),
495 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 + ],
496 640 'fct_editor_frame' => '',
497 641 ]);
498 642
499 643 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);