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