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 +154 -8 1.3.21 → 1.7.1 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,13 +125,85 @@
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'));
114 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 +
115 206 /*
116 207 * Disable Gutenberg for Products
117 208 */
118 209 add_filter('parent_file', function ($parent_file) {
@@ -206,9 +297,8 @@
206 297 'fluent_cart_products'
207 298 );
208 299 }
209 300
210 -
211 301 if (PermissionManager::hasPermission(['subscriptions/view'])) {
212 302 $submenu['fluent-cart']['subscriptions'] = array(
213 303 __('Subscriptions', 'fluent-cart'),
214 304 $capability,
@@ -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,15 +487,18 @@
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 496 'product_filter_options' => ProductFilter::getTableFilterOptions(),
394 - 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
395 - 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
497 + 'license_filter_options' => LicenseFilter::getTableFilterOptions(),
498 + 'license_site_filter_options' => LicenseSiteFilter::getTableFilterOptions(),
499 + 'tax_filter_options' => TaxFilter::getTableFilterOptions(),
500 + 'review_filter_options' => ReviewFilter::getTableFilterOptions(),
396 501 ];
397 502 $filterOptions = apply_filters('fluent_cart/admin_filter_options', $filterOptions, []);
398 503
399 504 // Structured table config keyed by table name (used by Table.js base class)
@@ -401,13 +506,35 @@
401 506 'order_table' => ['filters' => Arr::get($filterOptions, 'order_filter_options', [])],
402 507 'customers' => ['filters' => Arr::get($filterOptions, 'customer_filter_options', [])],
403 508 'product_table' => ['filters' => Arr::get($filterOptions, 'product_filter_options', [])],
404 509 'licenses' => ['filters' => Arr::get($filterOptions, 'license_filter_options', [])],
510 + 'license_sites' => ['filters' => Arr::get($filterOptions, 'license_site_filter_options', [])],
405 511 'taxes_table' => ['filters' => Arr::get($filterOptions, 'tax_filter_options', [])],
406 512 'subscriptions' => ['filters' => Arr::get($filterOptions, 'subscription_filter_options', [])],
407 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', [])],
408 518 ];
409 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 +
410 537 $tableConfig = apply_filters('fluent_cart/admin_table_saved_views', $tableConfig, [
411 538 'filterOptions' => $filterOptions
412 539 ]);
413 540
@@ -418,8 +545,9 @@
418 545 $appConfig['version'] = FLUENTCART_VERSION;
419 546 $appConfig['permissions'] = PermissionManager::getUserPermissions();
420 547 $appConfig['isProActive'] = App::isProActive();
421 548 $appConfig['proVersion'] = defined('FLUENTCART_PRO_PLUGIN_VERSION') ? FLUENTCART_PRO_PLUGIN_VERSION : null;
549 + $appConfig['fluentPlayer'] = FluentPlayerBridge::adminAppConfig();
422 550
423 551 $appConfig['logos'] = [
424 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
@@ -424,9 +552,9 @@
424 552 'dark' => Vite::getAssetUrl('images/logo/logo-full-dark.svg'),
425 553 'light' => Vite::getAssetUrl('images/logo/logo-full.svg'),
426 554 ];
427 555 $appConfig['isModuleTabEnabled'] = $settings->isModuleTabEnabled();
428 - $appConfig['upgrade_url'] = 'https://fluentcart.com/pricing/';
556 + $appConfig['upgrade_url'] = Helper::getUpgradeUrl();
429 557
430 558 $max_upload_size = wp_max_upload_size(); // Returns size in bytes
431 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 560 'app_config' => $appConfig,
@@ -431,8 +559,9 @@
431 559 $adminLocalizeData = apply_filters('fluent_cart/admin_app_data', [
432 560 'app_config' => $appConfig,
433 561 'slug' => $app->config->get('app.slug'),
434 562 'admin_url' => admin_url('admin.php?page=fluent-cart#/'),
563 + 'wp_admin_url' => rtrim(admin_url(), '/'),
435 564 'frontend_url' => URL::getFrontEndUrl(''),
436 565 'nonce' => wp_create_nonce($slug),
437 566 'rest' => $restVars,
438 567 'brand_logo' => $this->getMenuIcon(),
@@ -457,9 +586,9 @@
457 586 'order_statues' => Status::getOrderStatuses(),
458 587 'editable_order_statues' => Status::getEditableOrderStatuses(),
459 588 'editable_customer_statues' => Status::getEditableCustomerStatuses(),
460 589 'shipping_statuses' => Status::getShippingStatuses(),
461 - 'allow_bulk_payment_status_change' => true,
590 + // Attribute library powering the Advanced Variation feature.
462 591 'variation_attributes' => AttributeGroup::with(['terms'])->get(),
463 592 'variation_terms' => AttributeTerm::query()->get()->keyBy('id'),
464 593 'product_image_base_uri' => Helper::getProductImageBaseUri(),
465 594 'add_on_modules' => AddOnModule::showAddOns(),
@@ -481,17 +610,34 @@
481 610 'is_tax_enabled' => TaxModule::isTaxEnabled(),
482 611 'eu_vat_county_options' => TaxModule::euVatCountyOptions(),
483 612 'country_tax_titles' => TaxModule::taxTitleLists(),
484 613 'site_url' => site_url(),
614 + 'store_logo' => (new \FluentCart\Api\StoreSettings())->get('store_logo.url', ''),
485 615 'modules_settings' => ModuleSettings::getAllSettings(),
486 616 'purchase_fluent_cart_link' => 'https://fluentcart.com/',
487 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,
488 620 'subscription_intervals' => Helper::getAvailableSubscriptionIntervalOptions(),
489 621
490 - 'datei18' => TransStrings::dateTimeStrings(),
622 + 'datei18' => DayjsFormatter::localizedStrings(),
491 623 'el_strings' => TransStrings::elStrings(),
492 624 'wp_locale' => get_locale(),
493 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 + ],
494 640 'fct_editor_frame' => '',
495 641 ]);
496 642
497 643 wp_localize_script($slug . '_admin_app_start', 'fluentCartAdminApp', $adminLocalizeData);