orangecat / module-visibility
Guest access controls for Magento 2 B2B — force login redirect with URL whitelisting and selective price/cart hiding for guests
Package info
github.com/olivertar/m2_visibility
Type:magento2-module
pkg:composer/orangecat/module-visibility
Requires
- php: >=8.1
- magento/framework: *
- orangecat/core: *
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
- Overview
- Theme Compatibility
- Requirements
- Installation
- What Gets Installed
- Configuration
- Store Admin Guide
- Guest Experience
- Developer Guide
- REST API
- Frontend Routes Reference
- 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
POSTrequests 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 IGNOREis used so re-runningsetup:upgradenever 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
- Go to Stores → Configuration → Orangecat → Visibility (B2B).
- Under General Settings, set Enable Redirect to Login to Yes.
- Optionally change Target URL to a custom landing page (e.g.,
/company/account/createfor a B2B registration-first flow). - 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
- Keep Enable Redirect to Login set to No.
- 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.
- 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
- Click Add New Rule.
- Fill in:
- Label — unique descriptive name (e.g.,
My Custom Page). - URL Rule — the URL path to allow (e.g.,
/about-us). - Strategy —
Static Matchfor exact path comparison;Regex Matchfor pattern matching. - Store View — limit the rule to a specific store view, or leave as All Store Views.
- Label — unique descriptive name (e.g.,
- 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.00at the model level, and the rendered price boxes and amount containers are hidden via injectedstyle="display:none;". Theproduct:price:amountOpen Graph meta tag is also stripped from the HTML. - Hide Add to Cart: The
tocartform, Add to Cart button (by CSS class and byid="product-addtocart-button"), and the minicart wrapper are hidden via injectedstyle="display:none;". Any directPOSTto/checkout/cart/addby 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 inetc/di.xmlunder theStrategyManagerstrategiesarray argument with a unique identifier key. It appears automatically in the admin Strategy dropdown viaModel\Source\Strategy. - Extend catalog visibility to Hyvä/Breeze blocks — inject
VisibilityCheckerinto a new plugin targeting the theme-specific block class; callshouldHidePrice()orshouldHideAddToCart()to apply the same logic. Register inetc/frontend/di.xml. - Add a third operating mode — add config fields to
system.xml, add reader methods toVisibilityChecker, and wire new plugins;VisibilityCheckeris 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::whitelistOrangecat_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_idhas aCASCADE DELETEconstraint onstore.store_id. Deleting a store view removes its specific whitelist rules. Rules withstore_id = 0(all store views) are unaffected.- The
labelcolumn has a unique constraint. Duplicate labels are rejected at the database level. - The data patch uses
INSERT IGNOREto be idempotent — safe to runsetup:upgrademultiple 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_whitelisttable and its rows persist.