HomeJournal Admin › Ads

Ads

Journal-scoped banner-ad management — list, add/edit, delete, and drag-to-reorder ads shown on the journal's public site. Legacy entry: manager.php (role 6, _action=ads), dispatched into class/ads.class.php's AdsManager and rendered by view/ads.view.php.

Page Status E2E Enhanced Legacy Ref Route Roles
Ads List Full Yes filters manager.php?_action=ads /manager?_action=ads Journal Admin (role 6 — see gating note)
Ads — Add / Edit Full modal-dialog manager.php?_action=ads&_ts=0 /manager?_action=ads&_ts=0 Journal Admin (role 6 — see gating note)

Features

FeatureStatusE2EDescription
Admin can view the journal's ads in a list showing title, thumbnail, link, click count, last-updated date and status Full Yes List is journal-scoped (journal_code = current journal, ads_position <> -1) and ordered by ads_order with nulls last, code as fallback.
Admin can live-search ads by title New Yes 500ms-debounced, case-insensitive title search with a dedicated no-results state and clear-filters action.
Admin can filter the ads list by enabled/disabled status New Yes Filter combines with search; clearing both restores the full list.
Ads list paginates automatically as the admin scrolls New Infinite scroll with a 'loading more' indicator; total count surfaced in the filter card.
Admin can drag-to-reorder ads and the order is persisted Full New order is written to the server on each drop; failure rolls back to server state with an error toast.
Admin can create a new ad (title, link, note, image, status) Full Status defaults to Enabled; new ads land at the bottom of the list until reordered (legacy: null ads_order sorts last).
Admin can edit an existing ad with fields pre-filled Full Existing image is preserved unless a new one is uploaded or the image is explicitly cleared.
Admin can delete an ad after a confirmation prompt, from the list row or from the edit form Full Deleting also removes the stored image file, matching legacy's on-disk cleanup.
Admin sees secondary-language title and note fields when the journal is bilingual Full Both language values are stored ||-packed in one column, exactly as legacy encoded them.
Admin can upload an ad banner image with size and type limits and a live preview Full Validation is up-front in the picker instead of legacy's reject-after-upload flow (which notoriously deleted SVGs post-upload).
Admin can remove an ad's existing image without deleting the ad Full Legacy performed this immediately via manager.ajax.php deleteImage with no audit record; the port defers it to the save action.
Admin sees each ad's click count (read-only) Full Clicks are only incremented by public-facing code; the admin view is display-only, as in legacy.
Create, edit and delete of ads are recorded in the contact action history (audit types 64/65/66) Full Legacy gated all three audit writes — create, edit, and delete alike — on the save_cn_action setting via the shared saveCnActionHistory() early return; whether the port preserves that uniform gating was not verified from the web side.
System rejects a new ad whose primary-language title duplicates another ad in the same journal Dropped Legacy re-rendered the form with MNG_DUPLICATE_ADS_TITLE_ERR (and had a false-positive quirk on ||-packed titles); the port performs no duplicate check at all.
System auto-prepends http:// to ad links entered without a scheme Dropped A schemeless link is now a validation error at the form rather than silently rewritten server-side.
Role-4/16 holders can reorder ads or delete an ad's image via the AJAX gate without having page access Dropped The legacy privilege quirk (mutation access wider than page access) is intentionally collapsed into one uniform authorization check.
Ads pages are gated to the Journal Admin role Full Yes Legacy enforced access-scope and data-scope by independent rules; the port unifies both under tenant-scoped authorization.
Admin sees an explicit empty state with an 'add first ad' call-to-action when no ads exist New Yes Distinct from the filter-no-results state, which instead offers a clear-filters action.
Page gate is not journal-scoped, even though the data is.
Per apps/legacy/spec/admin/content-management/ads-list.md, both rows above require only a row in ju_contact_role with _role = 6 for any journal — the check does not restrict to the journal the admin is currently in. The list/form data itself is still filtered to ju_ads.journal_code = current journal by a separate rule, so access-scope and data-scope are enforced independently.
Reorder and image-delete are AJAX-only, not separate screens.
request/manager.ajax.php's reOrderAds and deleteImage handlers (AdsManager::reOrderAds / ::deleteImage) have no page of their own — they mutate ju_ads and the list re-renders in place — so they aren't counted as rows. That AJAX gate accepts _role IN (4, 6, 16), broader than the page gate's _role = 6, so a role-4/16 holder can reorder ads or delete an ad's image without being able to reach either row above.
Platform-wide Ads (journal_code = 0) is intentionally not claimed here.
mainm/adm.php and mainn/adm.php (and main/adm.php) also expose _action=ads, instantiating the same AdsManager with journalCode = 0 under the platform Super Admin session ($_SESSION['juAdmUser__'], via class/adm.class.php's JournalAdmin) rather than the tenant admin session this module's rows use. That variant is not tenant-scoped, so it doesn't fit this segment's "one journal's staff" remit; ultraport/inventory/super-admin/content-management.html already explicitly declined to claim it too ("News/Ads are left for whichever module or later triage pass is meant to claim them"). It remains an open gap for a later triage pass to give a home.
"Related Journal" rows share the table but not this module.
ju_ads rows with ads_position = -1 are not ads — they are cross-journal "Related Journal" links, explicitly excluded from the query above (ads_position <> -1) and managed instead by a wholly separate class/action: class/rel.journal.class.php's RelatedJournalManager (manager.php?_action=relj). That screen isn't named in any Journal Admin sibling module's brief (Editorial Board / News / Users / Profile / Account), so its home is unresolved here — an open flag, not a claim, consistent with this inventory's other open-flagged modules.