lindemannrock/craft-redirect-manager
最新稳定版本:5.13.0
Composer 安装命令:
composer require lindemannrock/craft-redirect-manager
包简介
Intelligent redirect management and 404 handling for Craft CMS
README 文档
README
Intelligent redirect management and 404 handling for Craft CMS.
⚠️ Beta Notice
This plugin is currently in active development and provided under the MIT License for testing purposes.
Licensing is subject to change. We are finalizing our licensing structure and some or all features may require a paid license when officially released on the Craft Plugin Store. Some plugins may remain free, others may offer free and Pro editions, or be fully commercial.
If you are using this plugin, please be aware that future versions may have different licensing terms.
Features
- Automatic 404 Handling - Catches all 404s and attempts to redirect
- Multiple Match Types:
- Exact Match - Case-insensitive exact URL matching
- RegEx Match - Full regular expression support
- Wildcard Match - Simple * wildcards
- Prefix Match - URL starts with pattern
- Rich Analytics - Track 404s with device detection, browsers, OS, geographic data, and bot identification
- Device Detection - Powered by Matomo DeviceDetector for accurate device, browser, and OS identification
- Bot Filtering - Identify and filter bot traffic (GoogleBot, BingBot, etc.)
- Geographic Detection - Track visitor location (country, city) via ip-api.com
- Auto-Redirect Creation - Automatically creates redirects when entry URIs change
- Smart Caching - File or Redis caching for fast redirect lookups and device detection
- Auto-Refreshing Dashboard - Configurable auto-refresh (5-60 seconds) with smart pause on user interaction
- CSV Export - Export comprehensive analytics including device and geo data
- Multi-Site Support - Site-specific or global redirects
- Plugin Integration - Pluggable architecture allowing other plugins to integrate 404 handling
- Privacy-First - IP hashing with salt, optional subnet masking, GDPR-friendly
- Logging Integration - Uses logging-library for consistent logs
Requirements
- Craft CMS 5.0 or later
- PHP 8.2 or later
- Logging Library 5.0 or greater (installed automatically as dependency)
- Matomo Device Detector 6.4 or greater (installed automatically as dependency)
Installation
Via Composer
cd /path/to/project
composer require lindemannrock/craft-redirect-manager
./craft plugin/install redirect-manager
Using DDEV
cd /path/to/project
ddev composer require lindemannrock/craft-redirect-manager
ddev craft plugin/install redirect-manager
Via Control Panel
In the Control Panel, go to Settings → Plugins and click "Install" for Redirect Manager.
Important: IP Privacy Protection
Redirect Manager uses privacy-focused IP hashing with a secure salt:
- ✅ Rainbow-table proof - Salted SHA256 prevents pre-computed attacks
- ✅ Unique visitor tracking - Same IP = same hash
- ✅ Maximum privacy - Original IPs never stored, unrecoverable
- ✅ Optional subnet masking - Additional anonymization layer
Setup Instructions:
- Generate salt:
php craft redirect-manager/security/generate-salt - Command automatically adds
REDIRECT_MANAGER_IP_SALTto your.envfile - Manually copy the salt value to staging/production
.envfiles - Never regenerate the salt in production
How It Works:
- Plugin automatically reads salt from
.env(no config file needed!) - Config file can override if needed:
'ipHashSalt' => App::env('REDIRECT_MANAGER_IP_SALT') - If no salt found, error banner shown in settings
Security Notes:
- Never commit the salt to version control
- Store salt securely (password manager recommended)
- Use the SAME salt across all environments (dev/staging/production)
- Changing the salt will break unique visitor tracking history
Local Development: Analytics Location Override
When running locally (DDEV, localhost), analytics will default to Dubai, UAE because local IPs can't be geolocated. To set your actual location for testing:
Option 1: Config File (recommended for project-wide default)
// config/redirect-manager.php return [ 'defaultCountry' => 'US', 'defaultCity' => 'New York', ];
Option 2: Environment Variable (recommended for per-environment control)
# .env
REDIRECT_MANAGER_DEFAULT_COUNTRY=US
REDIRECT_MANAGER_DEFAULT_CITY=New York
Fallback Priority:
- Config file setting
- .env variable
- Hardcoded default: Dubai, UAE
Supported locations:
- US: New York, Los Angeles, Chicago, San Francisco
- GB: London, Manchester
- AE: Dubai, Abu Dhabi (default: Dubai)
- SA: Riyadh, Jeddah
- DE: Berlin, Munich
- FR: Paris
- CA: Toronto, Vancouver
- AU: Sydney, Melbourne
- JP: Tokyo
- SG: Singapore
- IN: Mumbai, Delhi
Important: This setting is safe to use in all environments (dev, staging, production). It only affects private/local IP addresses (127.0.0.1, 192.168.x.x, 10.x.x.x, etc.). Real visitor IPs in production will always use actual geolocation from ip-api.com. This means you can safely commit config file settings without impacting production analytics.
Configuration
Config File
Create a config/redirect-manager.php file to override default settings:
cp vendor/lindemannrock/craft-redirect-manager/src/config.php config/redirect-manager.php
Example configuration:
<?php return [ // Auto create redirects when entry URIs change 'autoCreateRedirects' => true, // Analytics (master switch - controls device detection, geo, IP tracking) 'enableAnalytics' => true, 'enableGeoDetection' => false, // Track visitor location 'anonymizeIpAddress' => false, // Subnet masking for privacy // Analytics retention in days (0 = keep forever) 'analyticsRetention' => 30, 'analyticsLimit' => 1000, // Dashboard 'refreshIntervalSecs' => 5, // Auto-refresh interval (5, 15, 30, or 60 seconds) // Performance & Caching 'cacheStorageMethod' => 'file', // 'file' or 'redis' 'enableRedirectCache' => true, 'redirectCacheDuration' => 3600, // 1 hour 'cacheDeviceDetection' => true, 'deviceDetectionCacheDuration' => 3600, // 1 hour // Query String Handling 'stripQueryString' => false, // Strip query string for redirect matching 'preserveQueryString' => false, // Pass query string to destination URL 'stripQueryStringFromStats' => true, // Group analytics by path only // Log level 'logLevel' => 'error', ];
See Configuration Documentation for all available options.
Environment-Specific Config
<?php return [ '*' => [ 'autoCreateRedirects' => true, ], 'production' => [ 'logLevel' => 'error', 'analyticsRetention' => 90, ], 'dev' => [ 'logLevel' => 'debug', 'analyticsRetention' => 7, ], ];
Usage
Creating Redirects
Via Control Panel:
- Navigate to Redirect Manager → Redirects
- Click New redirect
- Fill in:
- Source URL:
/old-pageor/blog/*or regex pattern - Destination URL:
/new-pageor absolute URL - Match Type: exact, regex, wildcard, or prefix
- Status Code: 301 (permanent) or 302 (temporary)
- Source URL:
- Save
Programmatically:
use lindemannrock\redirectmanager\RedirectManager; RedirectManager::$plugin->redirects->createRedirect([ 'sourceUrl' => '/old-page', 'destinationUrl' => '/new-page', 'matchType' => 'exact', 'statusCode' => 301, 'enabled' => true, 'priority' => 0, 'siteId' => null, // null = all sites ]);
Match Type Examples
Exact Match:
Source: /old-page
Matches: /old-page (case-insensitive)
Wildcard Match:
Source: /blog/*
Matches: /blog/post-1, /blog/category/news, etc.
Prefix Match:
Source: /old-
Matches: /old-page, /old-blog, /old-anything
RegEx Match:
Source: ^/blog/(\d+)/(.*)$
Destination: /article/$1/$2
Viewing Analytics
Dashboard:
- Navigate to Redirect Manager → Analytics
- View handled vs unhandled 404s
- See most common 404s
- Create redirects from unhandled 404s with one click
Export CSV:
Redirect Manager → Analytics → Export CSV
Automatic Entry Redirects
When enabled (default), the plugin automatically creates redirects when:
- Entry slug changes:
/old-slug→/new-slug - Entry moves in structure:
/parent/child→/new-parent/child
Disable in settings: Redirect Manager → Settings → Auto Create Redirects
Query String Handling
Redirect Manager provides three settings to control how query strings are handled at different stages:
1. Strip Query String (Redirect Matching)
Controls whether query strings affect redirect matching.
OFF (default): Exact matching required
- Redirect rule:
/page /page✅ matches/page?foo=bar❌ does not match
ON: Query strings ignored during matching
- Redirect rule:
/page /page✅ matches/page?foo=bar✅ matches
2. Preserve Query String (Redirect Destination)
Controls whether query strings are passed to the destination URL.
OFF (default): Query strings dropped
- User visits:
/old?ref=email - Redirects to:
/new
ON: Query strings preserved
- User visits:
/old?ref=email - Redirects to:
/new?ref=email
3. Strip Query String From Stats (Analytics Grouping)
Controls how analytics groups URLs with different query strings.
ON (default): Consolidate by path
- Dashboard shows:
/page?source=google(count: 3) - Hits:
/page?source=email,/page?source=facebook,/page?source=google - All grouped into one record, displays latest query string
OFF: Separate records per unique URL+query
- Dashboard shows: 3 separate rows, each with count: 1
/page?source=email,/page?source=facebook,/page?source=google
Common Configurations
E-commerce/Marketing Sites:
'stripQueryString' => true, // Match any UTM params 'preserveQueryString' => true, // Keep tracking through redirects 'stripQueryStringFromStats' => true, // Consolidate analytics reports
API/Applications:
'stripQueryString' => false, // Exact URL matching required 'preserveQueryString' => false, // Canonical URLs without params 'stripQueryStringFromStats' => false, // Track each unique combination
Note: Analytics always store paths (e.g., /page?foo=bar), never full URLs with domains.
Testing Guide
1. Test Basic Redirect
# Create a test redirect via CP: # Source: /test-redirect # Destination: / # Match Type: exact # Status Code: 301 # Test it: curl -I http://your-site.test/test-redirect # Should return: HTTP/1.1 301 Moved Permanently # Location: http://your-site.test/
2. Test 404 Tracking
# Visit a non-existent page: curl -I http://your-site.test/page-that-does-not-exist # Check analytics: # CP → Redirect Manager → Analytics # Should show the 404 with "Handled: No"
3. Test Auto-Redirect Creation
- Create a test entry with slug
test-entry - Note the URL (e.g.,
/test-entry) - Change the slug to
renamed-entry - Save the entry
- Check Redirect Manager → Redirects
- Should see auto-created redirect:
/test-entry→/renamed-entry
4. Test Wildcard Matching
# Create redirect: # Source: /old-blog/* # Destination: /blog/ # Match Type: wildcard curl -I http://your-site.test/old-blog/any-post # Should redirect to /blog/
5. Test Analytics Cleanup
# Run cleanup job manually: php craft queue/run # Or test the service directly: php craft console/controller/eval \ "echo lindemannrock\redirectmanager\RedirectManager::\$plugin->analytics->cleanupOldAnalytics();"
Permissions
- View redirects - View redirect list
- Create redirects - Create new redirects
- Edit redirects - Modify existing redirects
- Delete redirects - Remove redirects
- View analytics - Access 404 analytics
- View logs - Access plugin logs
- Manage settings - Change plugin settings
Logging
Redirect Manager uses the LindemannRock Logging Library for centralized logging.
Log Levels
- Error: Critical errors only (default)
- Warning: Errors and warnings
- Info: General information
- Debug: Detailed debugging (includes performance metrics, requires devMode)
Configuration
// config/redirect-manager.php return [ 'logLevel' => 'error', // error, warning, info, or debug ];
Note: Debug level requires Craft's devMode to be enabled. If set to debug with devMode disabled, it automatically falls back to info level.
Log Files
- Location:
storage/logs/redirect-manager-YYYY-MM-DD.log - Retention: 30 days (automatic cleanup via Logging Library)
- Format: Structured JSON logs with context data
- Web Interface: View and filter logs in CP at Redirect Manager → Logs
Log Management
Access logs through the Control Panel:
- Navigate to Redirect Manager → Logs
- Filter by date, level, or search terms
- Download log files for external analysis
- View file sizes and entry counts
- Auto-cleanup after 30 days (configurable via Logging Library)
Requires: lindemannrock/craft-logging-library plugin (installed automatically as dependency)
See docs/LOGGING.md for detailed logging documentation.
Troubleshooting
Redirects Not Working
-
Check plugin is installed:
php craft plugin/list
-
Check database tables exist:
php craft migrate/all --plugin=redirect-manager
-
Check logs:
CP → Redirect Manager → Logs -
Enable debug logging:
// config/redirect-manager.php return [ 'logLevel' => 'debug', ];
-
Clear caches:
php craft clear-caches/all
Analytics Not Recording
- Check Settings → Analytics → Enable Analytics is enabled (master switch)
- Check analytics limit hasn't been reached
- Ensure IP hash salt is configured (run:
php craft redirect-manager/security/generate-salt) - Check database:
SELECT * FROM redirectmanager_analytics
Auto-Redirects Not Creating
- Check Settings → General → Auto Create Redirects is enabled
- Check entry has a URI (not disabled, not in a disabled section)
- Check logs for any errors
Events
Listen to redirect events in your own plugins:
use lindemannrock\redirectmanager\services\RedirectsService; use lindemannrock\redirectmanager\events\RedirectEvent; use yii\base\Event; Event::on( RedirectsService::class, RedirectsService::EVENT_BEFORE_SAVE_REDIRECT, function(RedirectEvent $event) { // Modify redirect before saving $event->redirect['statusCode'] = 302; // Or prevent saving // $event->isValid = false; } );
Available events:
EVENT_BEFORE_SAVE_REDIRECTEVENT_AFTER_SAVE_REDIRECTEVENT_BEFORE_DELETE_REDIRECTEVENT_AFTER_DELETE_REDIRECT
Plugin Integration
Redirect Manager provides a pluggable architecture that allows other plugins to integrate their 404 handling, similar to how plugins use the Logging Library.
RedirectHandlingTrait - What It Provides
The trait provides 2 methods that other plugins can use:
| Method | Returns | Description |
|---|---|---|
handleRedirect404(string $url, string $source, array $context) |
?array |
Check if a redirect exists for a 404 URL |
createRedirectRule(array $attributes) |
bool |
Create a new redirect in Redirect Manager |
Important: The trait does NOT provide handler functions like handleDeletedItem() or handle404(). Those are examples of functions you write in your own plugin that call the trait's methods.
Integration Method 1: Handle 404s
When your plugin encounters a 404, check if Redirect Manager has a matching redirect.
Example implementation (you write this code):
use lindemannrock\redirectmanager\traits\RedirectHandlingTrait; class MyController extends Controller { use RedirectHandlingTrait; /** * Your custom 404 handler (not provided by trait) */ private function handle404(): Response { $url = Craft::$app->getRequest()->getUrl(); // Call the trait's handleRedirect404() method $redirect = $this->handleRedirect404($url, 'my-plugin', [ 'type' => 'custom-404', 'context' => 'additional-metadata' ]); if ($redirect) { // Redirect found! Use it return $this->redirect($redirect['destinationUrl'], $redirect['statusCode']); } // No redirect found, use your fallback return $this->redirect('/', 302); } }
What happens:
- Your plugin encounters a 404 (e.g.,
/my-plugin/xyzdoesn't exist) - You call
handleRedirect404()to check if a redirect exists - Redirect Manager searches for matching redirects
- Analytics are recorded with your plugin as the source
- Returns redirect data if found, or
nullif not
Integration Method 2: Push Redirects
Your plugin can automatically create redirects when certain events occur (item deleted, slug changed, link expired, etc.).
Example implementation (you write this code):
use lindemannrock\redirectmanager\traits\RedirectHandlingTrait; class MyService extends Component { use RedirectHandlingTrait; /** * Your custom deletion handler (not provided by trait) * * This is example code showing how YOU would implement auto-redirect creation. * The function name "handleDeletedItem" is just an example - name it whatever you want. */ public function handleDeletedItem($item): void { // Your business logic here if ($item->hits === 0) { return; // Don't create redirect for unused items } // Call the trait's createRedirectRule() method $this->createRedirectRule([ 'sourceUrl' => '/items/' . $item->slug, 'sourceUrlParsed' => '/items/' . $item->slug, 'destinationUrl' => '/items', 'matchType' => 'exact', // exact|regex|wildcard|prefix 'redirectSrcMatch' => 'pathonly', // pathonly|fullurl (REQUIRED) 'statusCode' => 301, 'siteId' => $item->siteId, 'enabled' => true, 'priority' => 0, 'creationType' => 'item-deleted', // What happened (max 50 chars) 'sourcePlugin' => 'my-plugin', // Your plugin handle in kebab-case (max 50 chars) ], true); // Show notification to user } }
Slug Change Example with Undo Detection:
use lindemannrock\redirectmanager\traits\RedirectHandlingTrait; class MyService extends Component { use RedirectHandlingTrait; public function handleSlugChange(string $oldSlug, MyElement $element): void { $oldUrl = '/items/' . $oldSlug; $newUrl = '/items/' . $element->slug; // Check for immediate undo (A→B→A within time window) if ($this->handleUndoRedirect($oldUrl, $newUrl, $element->siteId, 'item-slug-change', 'my-plugin')) { return; // Undo was detected and handled } // Create redirect with notification $this->createRedirectRule([ 'sourceUrl' => $oldUrl, 'sourceUrlParsed' => $oldUrl, 'destinationUrl' => $newUrl, 'matchType' => 'exact', 'redirectSrcMatch' => 'pathonly', 'statusCode' => 301, 'siteId' => $element->siteId, 'enabled' => true, 'priority' => 0, 'creationType' => 'item-slug-change', 'sourcePlugin' => 'my-plugin', ], true); // Show notification } }
Trait Methods:
createRedirectRule(array $attributes, bool $showNotification = false): bool
- Creates a redirect in Redirect Manager
$showNotification- Set totrueto show "Redirect created: X → Y" notification to user- Returns
trueon success,falseon failure
handleUndoRedirect(string $oldUrl, string $newUrl, int $siteId, string $creationType, string $sourcePlugin): bool
- Detects and handles immediate undo (flip-flop redirects within undo window)
- Example: Change A→B, then quickly change B→A - deletes the A→B redirect instead of creating B→A
- Respects Redirect Manager's "Undo Window" setting (30-240 minutes)
- Shows "Slug change undone - previous redirect removed." notification
- Returns
trueif undo was handled,falseotherwise
handleRedirect404(string $url, string $source, array $context = []): ?array
- Checks if Redirect Manager has a redirect for a 404 URL
- Returns redirect data if found,
nullotherwise
Important Constraints:
creationType- Maximum 50 characters (e.g.,'code-change','item-deleted','smart-link-expired')sourcePlugin- Maximum 50 characters, always kebab-case (e.g.,'shortlink-manager','smart-links','my-plugin')redirectSrcMatch- REQUIRED - Must be'pathonly'or'fullurl'elementId- (Optional) Element ID if redirect was created by element URI change. Used to track and clean up redirect chains.sourceUrl/sourceUrlParsed- Maximum 255 charactersdestinationUrl- Maximum 500 characters
Plugin Handle Format:
- ✅
'shortlink-manager'(correct) - ✅
'smart-links'(correct) - ❌
'ShortLink Manager'(wrong - never use title case) - ❌
'shortlink_manager'(wrong - use kebab-case, not snake_case)
What happens:
- Your plugin detects an event (deletion, slug change, etc.)
- You call
handleUndoRedirect()to check for immediate undo - If no undo, call
createRedirectRule()to create the redirect - Redirect Manager validates, saves, and shows notification (if enabled)
- Cache is invalidated
- Future requests to the old URL will be redirected
Source Plugin Tracking
All 404s tracked through external plugins are recorded with their source plugin identifier:
// The second parameter ('shortlink-manager') becomes the source $redirect = $this->handleRedirect404($url, 'shortlink-manager', [ 'type' => 'shortlink-not-found', 'code' => 'abc123' ]);
Analytics dashboard shows breakdown by source:
Redirect Manager: 145 (handled: 98)
ShortLink Manager: 67 (handled: 54)
Smart Links: 43 (handled: 38)
This helps you identify which plugin or area is generating the most 404s.
Real-World Example: ShortLink Manager
See how ShortLink Manager integrates:
404 Handling: RedirectController::redirectToNotFound()
- Calls
handleRedirect404()when shortlink not found - Executes redirect if found
- Falls back to configured URL if not
Auto-Redirect Creation: ShortLinksService
handleCodeChange()- Creates redirect when shortlink code changeshandleExpiredShortLink()- Creates redirect when shortlink expireshandleDeletedShortLink()- Creates redirect when shortlink deleted (if has traffic)
Benefits
✅ Centralized 404 Tracking - See all 404s across your entire site in one dashboard ✅ Auto-Healing - Broken links automatically fixed when redirects exist ✅ Source Attribution - Know which plugin or area is generating 404s ✅ Loose Coupling - Plugins work independently, integration is optional ✅ Easy Integration - Add trait, write your handlers, call the methods, done!
API
Services
use lindemannrock\redirectmanager\RedirectManager; // Redirects $plugin = RedirectManager::$plugin; $plugin->redirects->createRedirect([...]); $plugin->redirects->updateRedirect($id, [...]); $plugin->redirects->deleteRedirect($id); $plugin->redirects->findRedirect($fullUrl, $pathOnly); $plugin->redirects->handleExternal404($url, $context); // For plugin integration // Analytics $plugin->analytics->record404($url, $handled, $context); // Context tracks source plugin $plugin->analytics->getAllAnalytics($siteId, $limit); $plugin->analytics->getChartData($siteId, $days); $plugin->analytics->getDeviceBreakdown($siteId, $days); $plugin->analytics->getBrowserBreakdown($siteId, $days); $plugin->analytics->getOsBreakdown($siteId, $days); $plugin->analytics->getBotStats($siteId, $days); $plugin->analytics->getLocationFromIp($ip); $plugin->analytics->exportToCsv($siteId); // Device Detection $plugin->deviceDetection->detectDevice($userAgent); $plugin->deviceDetection->isMobileDevice($deviceInfo); $plugin->deviceDetection->isBot($deviceInfo); // Matching $plugin->matching->matches($matchType, $pattern, $url);
Support
- Documentation: https://github.com/LindemannRock/craft-redirect-manager
- Issues: https://github.com/LindemannRock/craft-redirect-manager/issues
- Email: support@lindemannrock.com
License
This plugin is licensed under the MIT License. See LICENSE for details.
Developed by LindemannRock
统计信息
- 总下载量: 42
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 5
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-10-26