Developer notes
Overview · Installation · Configuration · Usage · FAQ
Order of processing
Section titled “Order of processing”The add-on has priority 2900, so its get_route handler runs after the SEO add-on (1800) and before full_page_cache (20000).
init.phpkeeps the requested URI (runtime.kv_r404_uri), because the SEO add-on rewrites$_SERVER['REQUEST_URI']while routing.get_route(storefront, GET/HEAD, not AJAX/API/console, Apply redirect rules on):- URL resolved by core, or core already answered with a redirect (an SEO redirect, the lower-case or language redirect): only rules with Override existing pages. These come from a small Registry cache (
kv_r404_ovr); without override rules this is one cache read and no SQL. - URL not resolved (the request would become
_no_page): all rules. Exact rules are one indexed query bysource_key; wildcard and regex rules come from the Registry cachekv_r404_pat. A match sets the init result to a redirect ([INIT_STATUS_REDIRECT, url, '', permanent, external]); a 410 rule keeps_no_pageand answers 410.
- URL resolved by core, or core already answered with a redirect (an SEO redirect, the lower-case or language redirect): only rules with Override existing pages. These come from a small Registry cache (
dispatch_before_display(only when the controller status is 404): 404s that controllers return for resolved URLs (for example a disabled product whose SEO name still exists) get the same rule matching (redirect withfn_redirect()); a 410 rule changes the status code; everything else that is still a 404 is recorded.
Every redirect sends X-Kv-Redirect: rule <id>, which the test tool shows and which helps when debugging with curl -I.
Matching
Section titled “Matching”- Request path:
fn_get_request_uri()of the original URI (relative to the storefront, URL-decoded, no trailing slash), lower case. Query: tracking parameters removed (utm_*,gclid,gbraid,wbraid,fbclid,msclkid,dclid,yclid,mc_cid,mc_eid,_ga,_gl,igshid,ttclid,twclid,li_fat_id,srsltid), keys sorted. - Exact: the stored source is normalized the same way. Keys:
md5(path?query)andmd5(path); the rule with the query wins, then the storefront-specific one (Ultimate), the language-specific one, the priority, the rule ID. - Wildcard:
*becomes(.*), anchored, case-insensitive; matched against the path, or path + query when the pattern contains?. - Regex: the expression as entered (delimiter
~, flagi), on the path (path + query when it contains\?). Limits: 255 characters, compile check, nested quantifiers refused, at most 100 active regex rules,pcre.backtrack_limit10000 while user patterns run; a pattern that fails at runtime is skipped. - Language:
sl(request or URL), the language segment of the URL when the SEO add-on shows languages in URLs, else the default storefront language; indispatch_before_displayCART_LANGUAGE. - Targets: URL (relative paths are prefixed with the storefront location;
$1–$9are replaced), orproducts.view/categories.view/pages.viewthroughfn_url()(in Ultimate with the storefront’scompany_id). The query is passed when Pass the query string is on. A target equal to the request is never redirected.
Recording without database writes
Section titled “Recording without database writes”404s and rule hits are appended as one JSON line each to var/kv_storage/kv_redirect_404_manager/buffer.log (LOCK_EX; the file stops growing at 5 MB during floods). A shutdown function of a request that recorded something flushes the buffer when the last flush is older than 60 seconds; the add-on’s admin pages and the cron dispatch flush too. The flush takes a non-blocking lock, reads and truncates the buffer under the file lock, aggregates per URL and per rule, and writes one upsert per URL (?:kv_r404_log) and per rule (?:kv_r404_hits). Retention runs once a day during a flush.
Normal page views read nothing from the buffer and write nothing.
Full page cache (4.21)
Section titled “Full page cache (4.21)”- Redirects (
fn_redirect()exits) and 404/410 responses are never cached by the full page cache (it caches only status OK responses), so every request for an old URL reaches the add-on and is redirected or logged, also when the cache is on. - Pages that exist and are cached are served by the cache. An Override rule for such a page therefore purges the page cache when it is saved, enabled, disabled or deleted (
invalidateByTags(['addons'])), so it applies at once. - The add-on’s tables are added to
ignore_cache_tags(schemas/full_page_cache/varnish.post.php): log flushes, hit counters and rule edits send no BANs, and the rules cache never tags pages. - Nothing is printed into pages and no session is started for the log.
Data model
Section titled “Data model”| Table | Contents |
|---|---|
?:kv_r404_rules |
rule_id, company_id (Ultimate storefront, 0 = all), lang_code (‘’ = all), match_type (E/W/R), source, source_key, target_type (U/P/C/A), target, target_id, code (301/302/410), priority, override, keep_query, status, note, log_id, created_at, updated_at |
?:kv_r404_hits |
rule_id, hits, last_hit (separate, so counting never invalidates the rules cache) |
?:kv_r404_log |
log_id, company_id, url, url_key (unique per storefront), hits (visitors), bot_hits, first_seen, last_seen, referrer (scheme + host + path), ua_class (H/S/O of the last hit), status (N open / I ignored / R redirected), rule_id, suggestion columns |
No IP address, user ID or session ID is stored.
| Hook | Use |
|---|---|
get_route |
Override rules for resolved URLs; all rules for unresolved URLs; 410 |
dispatch_before_display |
Rules for controller-level 404s, 410 status, 404 recording |
init_templater_post |
Only while an upgrade synchronization is pending (Upgrade\Synchronizer) |
before_dispatch (static, License\Client) |
License notices and the status bar data in the admin panel |
Both hook calls are identical in 4.17.2 and 4.21.2 (fn_set_hook('get_route', $req, $result, $area, $is_allowed_url), fn_set_hook('dispatch_before_display')).
License check
Section titled “License check”Tygh\Addons\KvRedirect404Manager\License\Client (generated) verifies the license. Gate points: fn_kv_redirect_404_manager_is_licensed() in both storefront hooks, checked only when there is something to do (a 404, or an override rule exists). It reads stored state only; the storefront never contacts the license server. Not gated: the admin pages, the test tool, CSV import/export and the data.
Console
Section titled “Console”php admin.php --dispatch=kv_redirect_404_manager.cronprints the flush result as JSON (lines, urls, hits, locked).
File map
Section titled “File map”| Path | Contents |
|---|---|
app/addons/kv_redirect_404_manager/addon.xml |
Settings, tables, uninstall function |
init.php, func.php |
Hook registration, URI capture, storefront hook handlers, settings info, fn_kv_redirect_404_manager_is_licensed() |
Tygh/Addons/KvRedirect404Manager/Engine.php |
Rule caches, matching, targets |
.../Recorder.php |
Buffer, user agent classes, flush, retention |
.../Rules.php |
Rule validation, save/delete/status, loop and chain detection, CSV import/export |
.../Log.php |
404 log list, actions, suggestions, CSV export |
.../Tester.php |
Test tool and live check |
.../Url.php |
URL normalization helpers |
License/, controllers/backend/kv_redirect_404_manager_license.php, license/*.tpl |
License check (generated) |
Upgrade/Synchronizer.php |
Self-healing upgrades (generated) |
controllers/backend/kv_redirect_404_manager.php |
Admin pages, actions, cron |
schemas/menu, schemas/permissions, schemas/full_page_cache |
Menu item, privileges, cache tags |
design/backend/templates/addons/kv_redirect_404_manager/views/... |
Rules, rule form, 404 log, test, import pages |
design/backend/css/addons/kv_redirect_404_manager/styles.less |
Admin styles (scoped .kv-r404) |