feat(store): distinguish missing identity from offline player
When the account has neither a Steam id nor a server auth id, the
store cannot identify the player in the game at all, but RconGive
returned playerOffline and the UI asked the user to join the server.
Add GiveResult::identityMissing, return it when both steam and
playerServerAuth are empty, and surface STORE_IDENTITY_MISSING from
the purchase match. playerOffline is now returned only when an
identifier exists but the player is not on the server.
fix(store): use Steam auth id and drop undefined from purchase POST
StorePurchaseService read $_SESSION[steam_id] (linked profile) instead
of $_SESSION[STEAMID] (Steam auth), so userIsOnline() missed players
who were online.
form_submit_ajax unconditionally read #username_inp, #steam_inp and
#server_auth; .val() returned undefined for absent fields and the
literal undefined went into every purchase request.
Prefer STEAMID when building GiveContext, coalesce the three lookups and ignore the literal undefined on the server side.
fix(store/give): fail explicitly on unresolved placeholders
PlaceholderResolver previously treated empty placeholder values
null as absent and let them slip through to str_ireplace,
so commands like tele %crossid% ... reached the game server with
literal tokens and the game replied player not found.
- add PlaceholderNotResolvedException with the missing placeholder list
- collect unresolved placeholders before substitution and throw
instead of emitting a half-built command
- catch it separately in RconGive and return GiveResult::notDeliverable
instead of misleading serverOffline
- persist the failure via PurchaseLog::writePlaceholderFailure
fix(confirm): remove double [c] offset in generate()
$cN = database_columns[c] already unwraps the section, but
generate() accessed $cN[c][table] 14 null !92 Incorrect table
name. remove()/get() are correct; only generate() had the bug.
refactor(store): migrate templates to {url} Smarty function
- 12 templates migrated from {'fn'|cfg}store/... to {url name=...}
- StoreModalController: drop action/url_delete assigns
- POST /store/itemID-N replaces POST /store + itemID in body
- admin_modal_group_edit: use route names instead of URL concat
fix(kernel): set legacy globals for Symfony-routed requests
Kernel::bootstrap now sets $GLOBALS['config'], ['tpl'], ['coins'],
['rub'], ['posts'], ['pm'], ['banned'] 14 FrontRunner used to do
this for legacy-routed requests. Symfony controllers skip it, so
legacy code reading globals (Connect, GameAuth, Facade) failed
with "Cannot assign null to property ...".
Fixes: /store purchase (RCON), /store/package (ServerConnect),
and any Symfony route touching legacy global state.
feat(template): add Smarty {url} function backed by UrlGenerator
- ContainerFactory: static getUrlGenerator() with lazy RequestContext,
setRequestContext() called from Kernel::bootstrap
- Template::helpers: {url name='...' params=[...]} !92 ABSOLUTE_URL
- CLI/install fallback to http://localhost (no Store templates there)
fix(store): register 'store' service alias for home=store config
RequestHandler::runLegacy resolves home page via $container->has($page).
Legacy store.php handled this; after extraction, needs a service with
id 'store' pointing to StoreCatalogController. Admin can still switch
home to any other page via config.
fix(http): fallback to legacy on transient container errors
When the DI container fails to load a newly deployed class before
autoload catches up, dispatch to the legacy router instead of
returning 500. Prevents first-request errors after deploy.