Comment to 'UNA 14 Timeline Cleanup Script – Expert Review Requested'
  • Understood on UNA CMS 14.x. Yes—once you paste the (type, action) distribution output, I can help you narrow it to a precise, low-risk WHERE clause that matches your instance’s real event taxonomy (and avoids collateral deletes).

    Below is the exact workflow I recommend so the WHERE clause is grounded in your data, not assumptions.

    1) Crafting the precise WHERE clause from your distribution query

    Step A — Confirm what you’re actually deleting (taxonomy + sample rows)

    Run the distribution query you mentioned:

    SELECT `type`, `action`, COUNT(*) AS `cnt`
    FROM `bx_timeline_events`
    GROUP BY `type`, `action`
    ORDER BY `cnt` DESC;
    

    Then, for the top 5–10 combos (and any combo you think are user “posts”), pull a small sample to validate they are the intended target:

    SELECT `id`, `type`, `action`, `owner_id`, `object_id`, `content`, `date`
    FROM `bx_timeline_events`
    WHERE `type` = 'X' AND `action` = 'Y'
    ORDER BY `date` DESC
    LIMIT 20;
    

    This is what prevents the classic failure mode: deleting things that look like “posts” but are actually system-generated events, module activity, profile changes, etc.

    Step B — Build a “candidate delete set” filter, then tighten it

    In almost every UNA Timeline cleanup, the final WHERE ends up being:

    1. Age cutoff
    2. One or more specific (type, action) pairs
    3. Explicit exclusions for system/admin/sticky/promoted/special events (based on what columns/flags exist in your schema)

    A safe template (to be adapted to your actual columns/taxonomy) looks like:

    WHERE `date` < {CUTOFF_UNIX_TS}
      AND (
            (`type` = '...' AND `action` = '...')
         OR (`type` = '...' AND `action` = '...')
         OR (`type` = '...' AND `action` = '...')
      )
      AND `owner_id` > 0
      -- Optional, only if these columns exist in your install:
      -- AND `system` = 0
      -- AND `sticked` = 0
      -- AND `promoted` = 0
      -- AND `pinned` = 0
    ;
    

    Step C — Prove the WHERE clause before deleting anything

    Before you run the deleter, validate with:

    SELECT COUNT(*) AS `to_delete`
    FROM `bx_timeline_events`
    <WHERE_CLAUSE>;
    

    And then eyeball a stratified sample:

    SELECT `id`, `type`, `action`, `owner_id`, `date`
    FROM `bx_timeline_events`
    <WHERE_CLAUSE>
    ORDER BY `date` DESC
    LIMIT 200;
    

    If you paste your distribution output, I’ll translate it into the minimal, correct WHERE for your environment (including exclusions that match what’s actually present in your schema).

    2) Symlinked/NFS storage: will bx_srv('bx_timeline','delete',[$iId]) remove media?

    What should happen

    In UNA, deleting via the module service (bx_srv('bx_timeline','delete',[$eventId])) is the right call because it is expected to:

    • delete the timeline event row
    • delete any module-specific media references
    • call into the storage layer to remove stored files (and their DB records)

    So, in principle, the service delete is the mechanism that should properly clean up associated media—regardless of whether the underlying storage path is “local” or happens to be a symlink pointing at NFS.

    Why symlink/NFS can still leave leftovers

    Even with the correct service call, you can still end up with orphaned files if any of the following are true:

    • Permission/ownership mismatch on the NFS mount (PHP user can delete DB rows but cannot unlink the remote file).
    • realpath/symlink path validation inside the storage engine behaves unexpectedly (less common, but possible in hardened filesystem checks).
    • NFS latency / transient I/O errors during unlink cause the physical delete to fail while the DB transaction succeeds (or vice-versa).
    • Your timeline attachments are stored in a different storage object than you think (e.g., separate storage objects for photos/videos/files), and some objects are not being invoked by that specific deletion path (version/module variations).

    The practical answer: rely on service delete, but add verification + “orphan sweep”

    I recommend treating this as two-phase:

    1. Primary deletion via bx_srv('bx_timeline','delete', ...) (what you are already doing)
    2. Post-run verification that catches orphaned storage records/files, and optionally deletes them via the storage API (not manual unlink())

    Verification approach (DB-level)

    After deleting a small test batch, check whether storage DB rows tied to timeline are decreasing as expected.

    Because table/object names can vary by install, the most robust approach is:

    • Identify the storage object(s) used by timeline attachments via:
    SELECT *
    FROM `sys_objects_storage`
    WHERE `object` LIKE '%timeline%';
    

    Then for any relevant storage object, inspect its files table (commonly sys_storage_files or bx_storage_files, depending on UNA generation/customization). If your install uses sys_storage_files, you can usually filter by storage_id and confirm rows aren’t piling up without owners.

    If you paste the result of the sys_objects_storage query (just the rows that match timeline), I can give you the exact orphan-detection SQL for your schema.

    If you do need additional cleanup

    Do not delete by filesystem path. Instead, delete orphans by calling the storage object’s delete method on file IDs. That ensures thumbnails/transcodings/variants and DB rows are handled correctly.

    The “right” cleanup step is therefore:

    • find orphaned file IDs (DB query)
    • for each file ID, call the storage API delete for that storage object

    I can provide a small, safe “orphan sweeper” routine once you confirm:

    • the storage object name(s) for timeline, and
    • the storage files table name in your install

    What to paste next (and I’ll turn it into the exact WHERE)

    1. Output of:
    SELECT `type`, `action`, COUNT(*) AS `cnt`
    FROM `bx_timeline_events`
    GROUP BY `type`, `action`
    ORDER BY `cnt` DESC;
    
    1. Output of:
    SELECT `object`, `engine`, `params`
    FROM `sys_objects_storage`
    WHERE `object` LIKE '%timeline%';
    

    With those two, I’ll:

    • produce the precise WHERE clause for your setup (including the safest exclusions), and
    • tell you whether your symlink/NFS case should be fully covered by service delete or whether an orphan sweep is advisable in your environment.