PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
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 1.3.19 trunk All 48 releases
← All changes | app/Hooks/Handlers/MenuHandler.php +135 -8 1.3.19 → 1.6.5 View file →
@@ -19,17 +19,27 @@
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\Modules\Shipping\Services\Filter\ShippingClassFilter;
36 +use FluentCart\App\Modules\Shipping\Services\Filter\ShippingZoneFilter;
29 37 use FluentCart\App\Modules\Integrations\AddOnModule;
38 +use FluentCart\App\Services\DateTime\DayjsFormatter;
30 39 use FluentCart\App\Services\Translations\TransStrings;
31 40 use FluentCart\App\Services\Permission\PermissionManager;
41 +use FluentCart\Database\DataBackfills;
32 42 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
33 43
34 44 class MenuHandler
35 45 {
@@ -42,8 +52,9 @@
42 52
43 53 if ($page == 'fluent-cart') {
44 54
45 55 \FluentCart\App\Events\FirstTimePluginActivation::handle();
56 + (new AdminFooter())->register();
46 57 add_action('admin_enqueue_scripts', function () {
47 58 Vite::enqueueStyle('fluent_cart_admin_app_css',
48 59 'styles/tailwind/style.css',
49 60 );
@@ -54,8 +65,14 @@
54 65
55 66 }
56 67 });
57 68
69 + // Inline dark mode init script for all FluentCart admin pages (prevents flicker)
70 + add_action('admin_head', [$this, 'outputFluentCartDarkModeScript']);
71 +
72 + // Enqueue dark mode toggle JS for taxonomy pages
73 + add_action('admin_head', [$this, 'enqueueDarkModeToggleForTaxonomy']);
74 +
58 75 // Add a post display state for special WC pages.
59 76 add_filter('display_post_states', array($this, 'addDisplayPostStates'), 10, 2);
60 77
61 78 }
@@ -106,8 +123,66 @@
106 123 return $states;
107 124
108 125 }
109 126
127 + public function outputFluentCartDarkModeScript()
128 + {
129 + global $pagenow;
130 + $page = $_GET['page'] ?? '';
131 + $postType = $_GET['post_type'] ?? '';
132 +
133 + // post.php?post=ID&action=edit omits post_type; resolve it from the post ID.
134 + if ($pagenow === 'post.php' && empty($postType)) {
135 + $postId = (int) App::request()->get('post', 0);
136 + $postType = $postId ? (string) get_post_type($postId) : '';
137 + }
138 +
139 + $isFluentCartPage = $page === 'fluent-cart';
140 + $isTaxonomyList = $pagenow === 'edit-tags.php' && $postType === FluentProducts::CPT_NAME;
141 + $isTermEditPage = $pagenow === 'term.php' && $postType === FluentProducts::CPT_NAME;
142 + $isProductEditPage = ($pagenow === 'post.php' || $pagenow === 'edit.php') && $postType === FluentProducts::CPT_NAME;
143 +
144 + if (!$isFluentCartPage && !$isTaxonomyList && !$isTermEditPage && !$isProductEditPage) {
145 + return;
146 + }
147 +
148 + // Runs synchronously in <head> before any paint — applies the dark class and
149 + // data-fct-theme attribute to <html> so logos and the theme icon render correctly
150 + // from the very first frame, with no light-to-dark flash.
151 + echo '<script>' . $this->getDarkModeInitScript() . '</script>';
152 + }
153 +
154 + public function enqueueDarkModeToggleForTaxonomy()
155 + {
156 + global $pagenow;
157 + $postType = $_GET['post_type'] ?? '';
158 +
159 + $isTaxonomyList = $pagenow === 'edit-tags.php' && $postType === FluentProducts::CPT_NAME;
160 + $isTermEditPage = $pagenow === 'term.php' && $postType === FluentProducts::CPT_NAME;
161 +
162 + if ($isTaxonomyList || $isTermEditPage) {
163 + Vite::enqueueScript(
164 + 'fluent_cart_dark_mode_toggle',
165 + 'admin/dark-mode-toggle.js',
166 + [],
167 + FLUENTCART_VERSION,
168 + true
169 + );
170 + }
171 + }
172 +
173 + private function getDarkModeInitScript(): string
174 + {
175 + return "(function(){" .
176 + "var k='fluent_theme_mode',c='fluent_theme_dark'," .
177 + "s=localStorage.getItem(k)||localStorage.getItem('fcart_admin_theme')," .
178 + "t=s==='dark'?'dark':s==='light'?'light':'system'," .
179 + "d=s==='dark'||s==='system:dark'||((!s||s==='system')&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);" .
180 + "document.documentElement.setAttribute('data-fct-theme',t);" .
181 + "if(d)document.documentElement.classList.add(c);" .
182 + "})();";
183 + }
184 +
110 185 public function init()
111 186 {
112 187
113 188 add_action('admin_menu', array($this, 'addAdminMenu'));
@@ -206,9 +281,8 @@
206 281 'fluent_cart_products'
207 282 );
208 283 }
209 284
210 -
211 285 if (PermissionManager::hasPermission(['subscriptions/view'])) {
212 286 $submenu['fluent-cart']['subscriptions'] = array(
213 287 __('Subscriptions', 'fluent-cart'),
214 288 $capability,
@@ -349,8 +423,11 @@
349 423 }
350 424
351 425 public function renderAdminMenu()
352 426 {
427 + if (!apply_filters('fluent_cart/show_admin_top_bar', true)) {
428 + return;
429 + }
353 430 AdminHelper::getAdminMenu(true);
354 431 }
355 432
356 433 public function pushProductNav($post)
@@ -371,13 +448,22 @@
371 448
372 449 do_action('fluent_cart/loading_app', $app);
373 450
374 451 //This should be done before enqueueing the global script.
375 - Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks']);
452 + // wp-i18n backs the admin translator (resources/admin/utils/translator/Translator.js).
453 + Vite::enqueueScript($slug . '_admin_app_start', 'admin/bootstrap/app.js', [$slug . '_global_admin_hooks', 'wp-i18n']);
376 454
377 455 //Don't register this script using vite.
378 - wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), [], FLUENTCART_VERSION, true);
456 + if (!wp_script_is('wp-hooks', 'registered')) {
457 + $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
458 + wp_register_script('wp-hooks', includes_url('js/dist/hooks' . $suffix . '.js'), ['wp-polyfill'], FLUENTCART_VERSION, true);
459 + }
460 + if (!wp_script_is('wp-hooks', 'enqueued')) {
461 + wp_enqueue_script('wp-hooks');
462 + }
379 463
464 + wp_enqueue_script($slug . '_global_admin_hooks', Vite::getEnqueuePath('admin/admin_hooks.js'), ['wp-hooks'], FLUENTCART_VERSION, true);
465 +
380 466 $manager = GatewayManager::getInstance();
381 467 $payment_routes = $manager->getRoutes();
382 468 // $payment_routes = apply_filters('fluent_cart/payments/payment_method_settings_routes', []);
383 469
@@ -385,15 +471,17 @@
385 471
386 472 $settings = new StoreSettings();
387 473 $checkoutUrl = add_query_arg(Helper::INSTANT_CHECKOUT_URL_PARAM, '=', $settings->getCheckoutPage());
388 474 $restVars = Helper::getRestInfo();
475 + $hasDataMigrations = current_user_can('manage_options') && DataBackfills::hasPending();
389 476
390 477 $filterOptions = [
391 478 'order_filter_options' => OrderFilter::getTableFilterOptions(),
392 479 'customer_filter_options' => CustomerFilter::getTableFilterOptions(),
393 480 'product_filter_options' => ProductFilter::getTableFilterOptions(),
394 - 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
395 - 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
481 + 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
482 + 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
483 + 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
396 484 ];
397 485 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
398 486
399 487 // Structured table config keyed by table name (used by Table.js base class)
@@ -401,13 +489,34 @@
401 489 'order_table' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
402 490 'customers' => ['filters' => Arr::get($filterOptions, 'customer_filter_options', [])],
403 491 'product_table' => ['filters' => Arr::get($filterOptions, 'product_filter_options', [])],
404 492 'licenses' => ['filters' => Arr::get($filterOptions, 'license_filter_options', [])],
493 + 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
405 494 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
406 495 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
407 496 'shipping_zone_table' => ['filters' => Arr::get($filterOptions, 'shipping_zone_filter_options', [])],
497 + // The Order Sources report filters orders, so its advanced-filter UI
498 + // reuses the Orders filter vocabulary rather than defining its own.
499 + 'source_report' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
408 500 ];
409 501
502 + // Tables that declare sort options on their filter class but carry no
503 + // filter-options entry above: the Sort popover still needs them, and
504 + // they are what `fluent_cart/{filterName}_table_sorts` feeds.
505 + $sortOnlyTables = [
506 + 'coupon_table' => CouponFilter::class,
507 + 'log_table' => LogFilter::class,
508 + 'order_bump_table' => OrderBumpFilter::class,
509 + 'shipping_class_table' => ShippingClassFilter::class,
510 + 'shipping_zone_table' => ShippingZoneFilter::class,
511 + ];
512 +
513 + foreach ($sortOnlyTables as $tableName => $filterClass) {
514 + if (empty($tableConfig[$tableName]['filters']['sorts'])) {
515 + $tableConfig[$tableName]['filters']['sorts'] = call_user_func([$filterClass, 'getSortOptions']);
516 + }
517 + }
518 +
410 519 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
411 520 'filterOptions' => $filterOptions
412 521 ]);
413 522
@@ -418,8 +527,9 @@
418 527 $appConfig['version'] = FLUENTCART_VERSION;
419 528 $appConfig['permissions'] = PermissionManager::getUserPermissions();
420 529 $appConfig['isProActive'] = App::isProActive();
421 530 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
531 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
422 532
423 533 $appConfig['logos'] = [
424 534 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 535 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -424,9 +534,9 @@
424 534 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 535 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
426 536 ];
427 537 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
428 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
538 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
429 539
430 540 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
431 541 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 542 'app_config' => $appConfig,
@@ -431,8 +541,9 @@
431 541 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 542 'app_config' => $appConfig,
433 543 'slug' => $app->config->get('app.slug'),
434 544 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
545 + 'wp_admin_url' => rtrim(admin_url(), '/'),
435 546 'frontend_url' => URL::getFrontEndUrl(''),
436 547 'nonce' => wp_create_nonce($slug),
437 548 'rest' => $restVars,
438 549 'brand_logo' => $this->getMenuIcon(),
@@ -457,9 +568,9 @@
457 568 'order_statues' => Status::getOrderStatuses(),
458 569 'editable_order_statues' => Status::getEditableOrderStatuses(),
459 570 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
460 571 'shipping_statuses' => Status::getShippingStatuses(),
461 - 'allow_bulk_payment_status_change' => true,
572 + // Attribute library powering the Advanced Variation feature.
462 573 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
463 574 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
464 575 'product_image_base_uri' => Helper::getProductImageBaseUri(),
465 576 'add_on_modules' => AddOnModule::showAddOns(),
@@ -484,14 +595,30 @@
484 595 'site_url' => site_url(),
485 596 'modules_settings' => ModuleSettings::getAllSettings(),
486 597 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
487 598 'admin_notices' => apply_filters('fluent_cart/admin_notices', []),
599 + // POSTs data-backfills/run until done; endpoint requires manage_options
600 + 'has_data_migrations' => $hasDataMigrations,
488 601 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
489 602
490 - 'datei18' => TransStrings::dateTimeStrings(),
603 + 'datei18' => DayjsFormatter::localizedStrings(),
491 604 'el_strings' => TransStrings::elStrings(),
492 605 'wp_locale' => get_locale(),
493 606 'is_full_name_required' => CheckoutFieldsSchema::isFullNameRequired(),
607 + 'business_details' => [
608 + 'company_name' => [
609 + 'enabled' => CheckoutFieldsSchema::isCompanyNameEnabled(),
610 + 'required' => CheckoutFieldsSchema::isCompanyNameRequired(),
611 + ],
612 + 'vat_number' => [
613 + 'enabled' => CheckoutFieldsSchema::isVatNumberEnabled(),
614 + 'required' => CheckoutFieldsSchema::isVatNumberRequired(),
615 + ],
616 + 'legal_registration_id' => [
617 + 'enabled' => CheckoutFieldsSchema::isLegalRegistrationIdEnabled(),
618 + 'required' => CheckoutFieldsSchema::isLegalRegistrationIdRequired(),
619 + ],
620 + ],
494 621 'fct_editor_frame' => '',
495 622 ]);
496 623
497 624 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);