orangecat/module-visibility

Guest access controls for Magento 2 B2B — force login redirect with URL whitelisting and selective price/cart hiding for guests

Maintainers

Package info

github.com/olivertar/m2_visibility

Type:magento2-module

pkg:composer/orangecat/module-visibility

Statistics

Installs: 20

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

0.0.2 2026-06-07 18:28 UTC

This package is auto-updated.

Last update: 2026-06-07 18:54:47 UTC


README

Guest access controls for Magento 2 B2B storefronts — force login redirect with URL whitelisting and selective price/cart hiding for guests.

Module: Orangecat_Visibility Version: 1.0.0 License: OSL-3.0 Author: Oliverio Gombert olivertar@gmail.com

Table of Contents

  1. Overview
  2. Theme Compatibility
  3. Requirements
  4. Installation
  5. What Gets Installed
  6. Configuration
  7. Store Admin Guide
  8. Guest Experience
  9. Developer Guide
  10. REST API
  11. Frontend Routes Reference
  12. DevOps & Integrator Notes

Overview

Orangecat_Visibility controls what unauthenticated (guest) visitors can see and access on a B2B storefront. It provides two independent, non-overlapping operating modes that can be enabled separately.

The module handles:

  • Force Login mode — redirect guests away from any page not on a URL whitelist, preserving the original URL so the visitor lands back on it after login.
  • Catalog Visibility mode — when Force Login is off, selectively hide product prices and/or the Add to Cart button for guests without blocking navigation.
  • URL Whitelist — a per-store, admin-managed table of URL rules with pluggable matching strategies (static exact match or regex). System-critical URLs (login, logout, forgot password, etc.) are pre-seeded and protected from accidental deletion.
  • Server-side cart protection — blocks guest POST requests to the Add to Cart controller even when the button is CSS-hidden, preventing bypass via direct HTTP calls.
  • Post-login redirect — after the guest authenticates, the browser returns to the page they originally requested.

Position in the Orangecat B2B Dependency Chain

This module is standalone and does not depend on Orangecat_Company. It can be installed independently on any Magento 2 storefront.

Orangecat_Core (via composer: orangecat/core)
  └── Orangecat_Visibility     ← this module (standalone)

Operating Modes

Mode Key Config What It Does
Force Login visibility/general/enabled = 1 Intercepts every frontend request; redirects guests unless the URL matches a whitelist rule.
Catalog Visibility visibility/general/enabled = 0 Force Login off; hide_price and/or hide_add_to_cart flags control catalog display for guests.

The two modes are mutually exclusive by design: when Force Login is active, the catalog visibility flags have no effect (guests are redirected before seeing any catalog content).

Theme Compatibility

Theme Status Notes
Luma Supported All plugins fully apply. Block interceptors target standard Magento catalog block classes present in Luma.
Hyvä Partial Force Login and pricing model plugins (return 0) work. PriceBoxPlugin and AmountPlugin work only if Hyvä still delegates to Magento\Framework\Pricing\Render\PriceBox/Amount. CatalogBlockPlugin may miss Hyvä Alpine.js listing components that do not extend the targeted block classes.
Breeze Evolution Partial Force Login works. Pricing plugins work. CatalogBlockPlugin targets Magento core block classes; Breeze-specific listing components may need additional interceptors. The minicart hide (minicart-wrapper class injection) may require verification.

For full Hyvä/Breeze Catalog Visibility support, add dedicated plugins targeting the theme-specific block classes and register them under the appropriate area in etc/frontend/di.xml. The VisibilityChecker service is theme-agnostic and can be injected directly.

Requirements

Dependency Version / Notes
PHP >= 8.1
Magento 2.4.x
Orangecat_Core composer: orangecat/core
Magento_Config core
Magento_Customer core
Magento_Catalog core
Magento_Checkout core
Magento_Sales core

Installation

Via Git Submodule (recommended for this project)

# From repo root
git submodule add git@github.com:olivertar/m2_visibility.git app/code/Orangecat/Visibility
git submodule update --init --recursive

Enable the Module

Run inside the PHP container (reward shell):

bin/magento module:enable Orangecat_Visibility
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

What Gets Installed

Database Tables

visibility_whitelist

Stores the URL rules used by the Force Login whitelist checker.

Column Type Notes
rule_id smallint, PK Auto-increment.
store_id smallint, FK store.store_id (CASCADE DELETE). 0 = all store views.
label varchar(255) Human-readable name. Unique constraint.
url_rule varchar(255) URL pattern; interpreted by the selected strategy.
strategy text Matcher identifier: default (static) or regex.
editable boolean true = admin-editable. false = system-protected, no Edit/Delete in UI.

Indexes: store_id (btree), editable (btree), url_rule + store_id (btree).

EAV Attributes

None.

Data Patches

AddDefaultWhitelistRules

Inserts 21 pre-seeded whitelist rules on first setup:upgrade. Rules with editable = 0 are system-protected and cannot be deleted from the admin UI.

Label URL Rule Strategy Editable
Rest API /rest regex yes
Customer Account Login /customer/account/login regex no
Customer Account Logout /customer/account/logout regex no
Customer Account Logout Success /customer/account/logoutSuccess regex no
Customer Account Create /customer/account/create regex no
Customer Account Create Post /customer/account/createpost static no
Customer Account Create Password /customer/account/createPassword regex no
Customer Account Forgot Password /customer/account/forgotpassword regex no
Customer Account Forgot Password Post /customer/account/forgotpasswordpost regex no
Customer Account Reset Password Post /customer/account/resetpasswordpost static no
Customer Section Load /customer/section/load regex no
Contact Us /contact regex yes
CMS Help /help regex yes
Sitemap.xml /sitemap.xml regex yes
Robots.txt /robots.txt regex yes
Varnish ESI url /page_cache/block/esi/blocks static no
Store-Switcher Redirect /stores/store/redirect static no
Store-Switcher Switch /stores/store/switch static no
Company Account Create /company/account/create regex no
Company Account Create Post /company/account/createpost static no
Company Account Success /company/account/success regex no

INSERT IGNORE is used so re-running setup:upgrade never duplicates existing rows.

Configuration

Path: Stores → Configuration → Orangecat → Visibility (B2B)

General Settings

Label Config Path Default Description
Enable Redirect to Login visibility/general/enabled No Master switch for Force Login mode. When Yes, all guest requests are intercepted.
Target URL visibility/general/target_url /customer/account/login URL guests are redirected to. Only shown when Enable is Yes.
visibility/general/enabled
visibility/general/target_url

Catalog Visibility

These settings apply only when Force Login is disabled. They have no effect when visibility/general/enabled = 1.

Label Config Path Default Description
Hide Price For Guest visibility/catalog/hide_price No Hide product prices for guests. Also suppresses <meta property="product:price:amount"> OG tags. When enabled, Add to Cart is hidden too (a guest who cannot see the price cannot buy).
Hide Add to Cart Button For Guest visibility/catalog/hide_add_to_cart No Hide Add to Cart button and minicart for guests independently of the price setting.
visibility/catalog/hide_price
visibility/catalog/hide_add_to_cart

Store Admin Guide

Accessing Settings

  • Whitelist rules: Admin menu → Visibility → Whitelist
  • Module configuration: Admin menu → Visibility → Settings (or Stores → Configuration → Orangecat → Visibility (B2B))

Enabling Force Login

  1. Go to Stores → Configuration → Orangecat → Visibility (B2B).
  2. Under General Settings, set Enable Redirect to Login to Yes.
  3. Optionally change Target URL to a custom landing page (e.g., /company/account/create for a B2B registration-first flow).
  4. Save Config and flush cache.

Once active, every guest who reaches the Magento application layer and requests a URL not on the whitelist will be redirected to the target URL.

Enabling Catalog Visibility Restrictions

  1. Keep Enable Redirect to Login set to No.
  2. Under Catalog Visibility:
    • Set Hide Price For Guest to Yes to remove prices from the catalog for guests.
    • Set Hide Add to Cart Button For Guest to Yes to hide cart controls without hiding prices.
  3. Save Config and flush cache.

When Hide Price is Yes, the Add to Cart button is automatically also hidden, regardless of the Add to Cart toggle, because allowing purchase without a visible price is illogical.

Managing the Whitelist

Path: Admin menu → Visibility → Whitelist

The grid shows all rules with columns: ID, Label, URL Rule, Strategy, Store View. Rows with editable = false show no Edit or Delete action links.

Adding a Rule

  1. Click Add New Rule.
  2. Fill in:
    • Label — unique descriptive name (e.g., My Custom Page).
    • URL Rule — the URL path to allow (e.g., /about-us).
    • StrategyStatic Match for exact path comparison; Regex Match for pattern matching.
    • Store View — limit the rule to a specific store view, or leave as All Store Views.
  3. Click Save Rule.

Matching Strategies

Strategy Identifier How It Works
Static Match default Exact path comparison (trailing slashes ignored): rtrim($path, '/') === rtrim($urlRule, '/').
Regex Match regex PHP preg_match with the pattern #<url_rule>#iU (case-insensitive, ungreedy).

Regex example: URL Rule /product/view with Regex Match will match /product/view/id/42 and any other path containing that substring. To match exactly, anchor the pattern: ^/product/view$.

Deleting a Rule

System-protected rules (editable = 0) cannot be deleted from the UI. To remove them, delete the row directly in the database or via a custom data patch.

Guest Experience

This module has no dedicated frontend pages or templates. Its effect is entirely behavioral:

Force Login mode active

  • Any URL request that is not on the whitelist triggers an immediate HTTP redirect to the configured target URL (default: the customer login page).
  • After successful login, the guest is redirected back to the page they originally requested. The original URL is saved in CustomerSession::setBeforeAuthUrl() before the redirect.
  • AJAX requests are not redirected (preventing broken JSON responses).
  • Static asset file extensions (.css, .js, .png, .jpg, .gif, .svg) bypass the check.

Catalog Visibility mode active (Force Login off)

  • Hide Price: Product prices appear as $0.00 at the model level, and the rendered price boxes and amount containers are hidden via injected style="display:none;". The product:price:amount Open Graph meta tag is also stripped from the HTML.
  • Hide Add to Cart: The tocart form, Add to Cart button (by CSS class and by id="product-addtocart-button"), and the minicart wrapper are hidden via injected style="display:none;". Any direct POST to /checkout/cart/add by a guest is blocked server-side with an error message and a redirect to the login page.

Logged-in customers are never affected by either mode.

Developer Guide

Module Structure

Orangecat/Visibility/
├── Api/
│   ├── Data/WhitelistEntryInterface.php       # Data object contract
│   └── Repository/WhitelistRepositoryInterface.php  # getCollection() only
├── Block/Adminhtml/Whitelist/Edit/            # Form button providers
├── Controller/Adminhtml/Whitelist/            # Admin CRUD controllers
├── Model/
│   ├── ResourceModel/WhitelistEntry/          # Collection + Grid collection
│   ├── ResourceModel/WhitelistEntry.php       # DB resource model
│   ├── Source/Strategy.php                    # Option source for strategy dropdown
│   ├── Strategy/                              # URL matching strategy pattern
│   │   ├── MatcherInterface.php
│   │   ├── StaticMatcher.php                  # Exact path match
│   │   ├── RegexMatcher.php                   # preg_match with #pattern#iU
│   │   └── StrategyManager.php                # DI-injected strategy registry
│   ├── WhitelistEntry.php                     # Entity model
│   ├── WhitelistEntry/DataProvider.php        # UI form data provider
│   └── VisibilityChecker.php                  # Central logic service
├── Plugin/
│   ├── FrontControllerPlugin.php              # aroundDispatch — force login redirect
│   ├── AfterLoginPlugin.php                   # afterExecute LoginPost — restore referer
│   ├── PreventAddToCartPlugin.php             # aroundExecute Cart\Add — server-side block
│   ├── Block/CatalogBlockPlugin.php           # afterToHtml — hides tocart/minicart HTML
│   └── Pricing/
│       ├── FinalPricePlugin.php               # afterGetValue — returns 0 for guests
│       ├── RegularPricePlugin.php             # afterGetValue — returns 0 for guests
│       └── Render/
│           ├── AmountPlugin.php               # afterToHtml — hides zero-amount containers
│           └── PriceBoxPlugin.php             # afterToHtml — hides zero-price boxes
├── Repository/WhitelistRepository.php
├── Setup/Patch/Data/AddDefaultWhitelistRules.php
├── Ui/Component/Listing/Column/WhitelistActions.php
├── etc/
│   ├── acl.xml
│   ├── config.xml
│   ├── db_schema.xml
│   ├── di.xml                                 # Preferences + StrategyManager + grid collection
│   ├── module.xml
│   ├── adminhtml/
│   │   ├── menu.xml
│   │   ├── routes.xml
│   │   └── system.xml
│   └── frontend/
│       └── di.xml                             # All frontend plugins registered here
├── view/adminhtml/
│   ├── layout/                                # whitelist_index, _new, _edit
│   └── ui_component/                          # whitelist_listing, whitelist_form
└── registration.php

Key Classes

Model/VisibilityChecker

Central service injected by all catalog visibility plugins. Uses FPC-safe Magento\Framework\App\Http\Context (not CustomerSession) to determine guest status, making it compatible with full-page cache.

Method Returns Description
isGuest(): bool bool True if CustomerContext::CONTEXT_AUTH is falsy in HTTP context.
isForceLoginEnabled(): bool bool Reads visibility/general/enabled config flag.
shouldHidePrice(): bool bool True when Force Login is off AND guest AND hide_price = 1.
shouldHideAddToCart(): bool bool True when Force Login is off AND guest AND (hide_price = 1 OR hide_add_to_cart = 1).

Api/Data/WhitelistEntryInterface

Constant Field Type
RULE_ID rule_id int
STORE_ID store_id int
LABEL label string
URL_RULE url_rule string
STRATEGY strategy string
EDITABLE editable bool

Standard getters/setters for each field.

Api/Repository/WhitelistRepositoryInterface

getCollection(): Collection

Returns the full WhitelistEntry\Collection. There is no getById, save, or delete in the service contract — CRUD for whitelist rules is handled by the admin controllers directly via the model factory.

Model/Strategy/MatcherInterface

getName(): string
isMatch(string $path, WhitelistEntryInterface $rule): bool

All matchers implement this interface. Registered via StrategyManager in etc/di.xml:

<item name="default" xsi:type="object">Orangecat\Visibility\Model\Strategy\StaticMatcher</item>
<item name="regex"   xsi:type="object">Orangecat\Visibility\Model\Strategy\RegexMatcher</item>

Plugins

All frontend plugins are declared in etc/frontend/di.xml (frontend area only).

Class Target Hook Purpose
FrontControllerPlugin Magento\Framework\App\FrontControllerInterface around Checks guest status; iterates whitelist; redirects if no rule matches. Skips AJAX and asset file paths.
AfterLoginPlugin Magento\Customer\Controller\Account\LoginPost after Overrides the post-login redirect to the referer URL if it is not the login page.
PreventAddToCartPlugin Magento\Checkout\Controller\Cart\Add around Blocks guest add-to-cart POST requests when either Force Login or Hide Cart is enabled.
FinalPricePlugin Magento\Catalog\Pricing\Price\FinalPrice after Returns 0 for getValue() when shouldHidePrice(). Also applied to Magento\ConfigurableProduct\Pricing\Price\FinalPrice.
RegularPricePlugin Magento\Catalog\Pricing\Price\RegularPrice after Returns 0 for getValue() when shouldHidePrice().
PriceBoxPlugin Magento\Framework\Pricing\Render\PriceBox after Injects style="display:none;" on the price-box container when the rendered amount is 0.0.
AmountPlugin Magento\Framework\Pricing\Render\Amount after Injects style="display:none;" on the price-container span when getDisplayValue() is 0.0.
CatalogBlockPlugin Magento\Catalog\Block\Product\View, ListProduct, Related, Crosssell, Upsell, Magento\Checkout\Block\Cart\Sidebar, Magento\CatalogWidget\Block\Product\ProductsList after Injects style="display:none;" into tocart elements, tocart-form forms, the PDP Add to Cart button, and the minicart wrapper. Removes product:price:amount meta tag when price is hidden.

Observers

This module registers no observers.

JS Components

This module provides no JavaScript components — all guest-restriction logic is server-side PHP.

Email Templates

This module sends no email.

ACL Resources

Resource ID Title Location
Orangecat_Visibility::base Visibility Under Orangecat_Core::menu
Orangecat_Visibility::whitelist Whitelist Under Visibility
Orangecat_Visibility::config Product Visibility Under Stores → Settings → Configuration

Adding Custom Logic

  • Add a new URL matching strategy — implement Model\Strategy\MatcherInterface, then register it in etc/di.xml under the StrategyManager strategies array argument with a unique identifier key. It appears automatically in the admin Strategy dropdown via Model\Source\Strategy.
  • Extend catalog visibility to Hyvä/Breeze blocks — inject VisibilityChecker into a new plugin targeting the theme-specific block class; call shouldHidePrice() or shouldHideAddToCart() to apply the same logic. Register in etc/frontend/di.xml.
  • Add a third operating mode — add config fields to system.xml, add reader methods to VisibilityChecker, and wire new plugins; VisibilityChecker is the single point of truth for all guest-state checks.

REST API

This module exposes no REST API endpoints. There is no etc/webapi.xml file.

Frontend Routes Reference

This module has no frontend routes. There is no etc/frontend/routes.xml file. All guest-facing behavior is enforced through plugins on existing Magento controllers and rendering classes.

DevOps & Integrator Notes

Deployment Checklist

# After deploying or updating this module:
bin/magento module:enable Orangecat_Visibility
bin/magento setup:upgrade          # creates visibility_whitelist table + seeds default rules
bin/magento setup:di:compile       # regenerates interceptors for all registered plugins
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

Integration Token Scope

This module manages frontend behavior only. No integration token permissions are required to interact with whitelist rules — all management is done via the Admin UI.

Minimum ACL permissions for admin users who need to manage the whitelist:

  • Orangecat_Visibility::whitelist
  • Orangecat_Visibility::config (for store configuration access)

Disabling Without Uninstalling

Set visibility/general/enabled = No and both catalog flags to No in store configuration. This disables all guest restrictions without removing any data.

bin/magento module:disable Orangecat_Visibility
bin/magento setup:upgrade
bin/magento cache:flush

Data Integrity

  • visibility_whitelist.store_id has a CASCADE DELETE constraint on store.store_id. Deleting a store view removes its specific whitelist rules. Rules with store_id = 0 (all store views) are unaffected.
  • The label column has a unique constraint. Duplicate labels are rejected at the database level.
  • The data patch uses INSERT IGNORE to be idempotent — safe to run setup:upgrade multiple times.
  • System-protected rules (editable = 0) are not deletable via the Admin UI, but can be removed directly in the database if needed. Removing them while Force Login is active may lock guests out of login or other essential flows.
  • No data is removed when the module is disabled. The visibility_whitelist table and its rows persist.