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