Skip to content

Developer notes

Overview · Installation · Configuration · Usage · FAQ

The add-on has priority 2900, so its get_route handler runs after the SEO add-on (1800) and before full_page_cache (20000).

  1. init.php keeps the requested URI (runtime.kv_r404_uri), because the SEO add-on rewrites $_SERVER['REQUEST_URI'] while routing.
  2. 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 by source_key; wildcard and regex rules come from the Registry cache kv_r404_pat. A match sets the init result to a redirect ([INIT_STATUS_REDIRECT, url, '', permanent, external]); a 410 rule keeps _no_page and answers 410.
  3. 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 with fn_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.

  • 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) and md5(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 ~, flag i), on the path (path + query when it contains \?). Limits: 255 characters, compile check, nested quantifiers refused, at most 100 active regex rules, pcre.backtrack_limit 10000 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; in dispatch_before_display CART_LANGUAGE.
  • Targets: URL (relative paths are prefixed with the storefront location; $1–$9 are replaced), or products.view / categories.view / pages.view through fn_url() (in Ultimate with the storefront’s company_id). The query is passed when Pass the query string is on. A target equal to the request is never redirected.

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.

  • 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.
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')).

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.

php admin.php --dispatch=kv_redirect_404_manager.cron

prints the flush result as JSON (lines, urls, hits, locked).

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)