Home › Journal 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
| Feature | Status | E2E | Description |
|---|---|---|---|
| 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.