OPS-013
Head code: let the operator paste what ad networks need
Ad networks require script tags in the head. Restricting what can go there makes the product unusable, so restrict who can set it instead — and keep popunder loaders off public pages.
An allowlist that permits only meta and link tags in an operator's head field sounds safe and makes the site unusable with real advertising, because nearly every network in this genre ships script loaders that must run in the head.
Move the protection from what to who. The field takes anything; only the owner role can edit it; every change goes in the audit log. A manager or support account sees it disabled, and the server refuses their save as well, so the restriction is not just a hidden input.
Then split it in two. Verification tags for search console and marketplaces must be on public pages. Advertising loaders — especially popunders — must not fire at signed-out visitors reading your landing page, so those live in a members-only field.
function head_extra(bool $signedIn): string
{
$out = '';
// Public: verification tags must be findable on the front page.
$out .= trim(setting('head_extra')) . "\n";
// Members only: ad loaders, popunders, anything that should never fire
// at a signed-out visitor reading a public page.
if ($signedIn) {
$out .= trim(setting('head_extra_members')) . "\n";
}
return $out; // emitted verbatim, by design
}
/** Who may change it — enforced on save, not just in the form. */
function owner_only_settings(): array
{
return ['head_extra', 'head_extra_members', 'fp_send_key', 'cron_key'];
}
function can_edit_setting(array $admin, string $key): bool
{
return !in_array($key, owner_only_settings(), true) || ($admin['role'] ?? '') === 'owner';
}
function save_setting(PDO $pdo, array $admin, string $key, string $value): bool
{
if (!can_edit_setting($admin, $key)) {
return false; // the server decides, not the form
}
if (strlen($value) > 20000) {
return false; // TEXT column, and a sane ceiling
}
setting_set($pdo, $key, $value);
audit($pdo, 'setting_change', 'setting', 0, $key . ' changed');
return true;
}
Using it
Render the field as a monospace textarea of ten rows or more. Operators paste blocks from seven networks at once, and a single-line input makes that miserable.
Store it in the database, not in a template file. A release that overwrites the template wipes their tags, and they will not notice until revenue drops.
Keep the public field for verification only, and say so in the help text.
What bites people
A popunder loader in the public head fires at every visitor including crawlers, which is both a ranking problem and the reason an operator gets complaints from people who never signed up.
Verbatim means verbatim: no escaping, no filtering. That is the point, and it is why the field is owner-only.
Cap the length. A TEXT column accepts a lot, and an accidental paste of an entire page will throw a database error on save.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.