Skip to content

feat: reprocess existing .elpx media attachments (#42)#43

Merged
erseco merged 6 commits into
mainfrom
feature/reprocess-existing-elpx
Jun 2, 2026
Merged

feat: reprocess existing .elpx media attachments (#42)#43
erseco merged 6 commits into
mainfrom
feature/reprocess-existing-elpx

Conversation

@erseco
Copy link
Copy Markdown
Contributor

@erseco erseco commented Jun 2, 2026

Summary

Closes #42.

Adds a safe, reusable way to (re)process .elpx attachments that already live in the Media Library but were never extracted — for example uploaded before the plugin was active, or stored through a flow such as Formidable Forms that bypasses the upload handler. In that state the attachment has no extraction directory and no eXeLearning metadata, so [exelearning id="123"] falls back to a download link instead of rendering the preview iframe.

What changed

  • ExeLearning_Reprocessor (new) — single home for the validate → extract → commit → cleanup primitives, plus reprocess(), needs_reprocessing(), is_elpx_attachment() and the candidate-query helpers. It extracts to a fresh directory before touching any metadata and removes the previous extraction only after the new one is committed, so it is idempotent and leaves existing content intact on failure.
  • REST API refactor — the save/create flows now delegate to the reprocessor instead of duplicating the extraction logic (single source of truth).
  • Media Library bulk action"Reprocess eXeLearning file" on upload.php, with an admin notice summarising reprocessed / skipped / failed counts.
  • WP-CLIwp exelearning reprocess --id=<id> | --all | --all --force.
  • Security .htaccess — exposed as a reusable static writer so reprocessed extraction roots are guarded even on sites that installed the plugin after files were uploaded.

Acceptance criteria

  • .elpx attachments without _exelearning_extracted can be processed after activation
  • Creates the extraction directory under wp-content/uploads/exelearning/<hash>/
  • Sets/updates _exelearning_extracted and _exelearning_has_preview consistently
  • After extraction, [exelearning id="ATTACHMENT_ID"] renders the preview iframe
  • Invalid / non-previewable .elpx returns a clear admin-facing error
  • Idempotent: running twice leaves no orphaned directories or inconsistent metadata
  • Existing extracted directories are cleaned up only after a new extraction succeeds
  • Works for regular Media Library uploads and for plugin/form integrations (Formidable Forms)

Testing

  • New ReprocessorTest (15 cases) and MediaLibraryReprocessTest (4 cases) cover every acceptance criterion, including the shortcode rendering the iframe after reprocessing and failure preserving the prior extraction.
  • Full suite: 635/636 passing. The single failure (StaticEditorInstallerTest::test_is_editor_installed_returns_false_when_missing) is pre-existing and unrelated — it fails because a local dist/static/ build is present, independent of this change.
  • PHPCS clean on all new/modified files.
  • WP-CLI command verified in the wp-env container (wp help exelearning reprocess and a real run).

Add a safe way to (re)process .elpx attachments that already live in the
Media Library but were never extracted — e.g. uploaded before the plugin
was active, or stored through a flow such as Formidable Forms that bypasses
the upload handler. Previously such attachments had no extraction directory
or eXeLearning metadata, so the [exelearning] shortcode fell back to a
download link instead of rendering the preview iframe.

- Add ExeLearning_Reprocessor: a single home for the validate -> extract ->
  commit -> cleanup primitives, with reprocess(), needs_reprocessing() and
  candidate-query helpers. Extracts to a fresh directory before touching any
  metadata and removes the previous extraction only on success, so it is
  idempotent and leaves existing content intact on failure.
- Refactor the REST API save/create flows to delegate to the reprocessor
  instead of duplicating the extraction logic.
- Add a "Reprocess eXeLearning file" bulk action to the Media Library plus an
  admin notice summarising the result.
- Add a WP-CLI command: wp exelearning reprocess --id=<id> | --all | --force.
- Expose the security .htaccess writer so reprocessed extraction roots are
  guarded even on sites that installed the plugin after files were uploaded.

Tested via new ReprocessorTest and MediaLibraryReprocessTest covering every
acceptance criterion (extraction + metadata, no-preview files, invalid files,
idempotency, failure preserving the prior extraction, the shortcode rendering
the iframe after reprocessing, and the candidate query).

Closes #42
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Test in WordPress Playground

Test the plugin with the code from this branch:

Preview in WordPress Playground

⚠️ The embedded eXeLearning editor is not included in this preview. You can install it from Settings > eXeLearning using the "Download & Install Editor" button. All other plugin features (ELP upload, shortcode, Gutenberg block, preview) work normally.

@erseco erseco self-assigned this Jun 2, 2026
erseco added 5 commits June 2, 2026 21:27
…I from coverage

- Regenerate exelearning.pot with the new reprocess feature strings.
- Translate them into es_ES (fixes the CI "Check untranslated strings" gate)
  and into all other shipped locales: ca, ca_valencia, de_DE, eo, eu, gl_ES,
  it_IT, pt_PT and ro_RO (with its 3 plural forms). Regenerate every .mo.
- Add MediaLibraryReprocessTest cases for render_reprocess_admin_notice
  (success, warning, and the two silent paths).
- Exclude includes/class-cli-command.php from coverage: it only loads under
  WP-CLI, which is unavailable to PHPUnit (same rationale as the existing
  exclusions). Overall line coverage stays at 79.36%.
eXeLearning source projects are ZIP archives whose only reliable signature is
an inner content.xml. A genuine project can land in the Media Library with a
.zip extension (renamed, or stored by a flow such as Formidable Forms). The
reprocessor previously rejected those purely by extension.

Broaden only the reprocessor (bulk action, WP-CLI, single --id) to also accept
.zip attachments, gated by content validation. The upload path, MIME
registration and UI stay .elpx-only, so backup zips and the plugin's own
web/SCORM/IMS exports (which have no content.xml) are never treated as
eXeLearning.

- Add ACCEPTED_EXTENSIONS (elpx, zip) and split detection into
  is_exelearning_candidate() (cheap extension check) and is_eligible()
  (content-validates .zip via ExeLearning_Elp_File_Service::validate_elp_file).
- reprocess() accepts .elpx or .zip; extraction still validates content and
  returns a clear error for a non-eXeLearning archive without writing metadata.
- needs_reprocessing()/scan queries match .elpx OR .zip and filter by
  is_eligible(), so plain archives are never auto-flagged.
- Bulk handler skips ineligible items (incl. plain zips); CLI uses the renamed
  candidate query and eligibility guard.

No new user-facing strings (reuse existing translated messages), so the i18n
gate is unaffected. New ReprocessorTest/MediaLibraryReprocessTest cases cover
valid vs plain .zip across reprocess, needs_reprocessing, the candidate query
and the bulk action. Line coverage 79.26%.
…#42)

Make the reprocessing capability discoverable where users actually click. In
the default grid view, opening an unprocessed .elpx/.zip showed nothing
eXeLearning and no way to act on it: the modal JS only ran when the prepared
attachment data carried the `exelearning` blob, which exists only once a file
is extracted.

- REST: add POST /exelearning/v1/reprocess/{id} (permission check_edit_permission)
  that delegates to ExeLearning_Reprocessor::reprocess() and returns the save
  response (preview_url) or a clear WP_Error. Reuses the existing content gate,
  so a non-eXeLearning .zip is rejected without writing metadata.
- Media library: expose `exelearningReprocessable` (unprocessed candidate the
  user may edit; cheap extension check, no per-item ZIP I/O while browsing) and
  localize a new `exelearningMediaSettings` object (REST root + wp_rest nonce)
  plus the new button strings.
- Modal JS: render a "Process as eXeLearning" button + "not processed yet" hint
  for reprocessable attachments (both the single-column details and the
  two-column attachment-info actions), call the REST endpoint with X-WP-Nonce,
  and on success refresh the attachment so the preview renders. Errors are shown
  inline (no blocking dialogs).

New strings translated in es_ES (CI gate) and all shipped locales; .pot/.mo
regenerated. Tests cover the REST route/handler (success, invalid, non-candidate,
permission) and the exelearningReprocessable flag. Verified end-to-end in the
browser: clicking the button on a .zip eXeLearning project extracts it and the
preview appears. Line coverage 79.51%.
…cept it

Reprocessing a .zip made it previewable, but the editor and export-bootstrap
paths are .elpx-only and aborted with "This file is not an eXeLearning file
(.elpx)." once the now-processed file showed an Edit/Download affordance.

Once extraction has confirmed the archive is a real eXeLearning project,
rename the underlying .zip to the canonical .elpx (unique filename) and update
the attachment via update_attached_file(). The file then behaves as a
first-class eXeLearning source everywhere — preview, edit, export and save —
with no changes to the existing .elpx extension guards. Embedding is by
attachment ID, so shortcodes keep working; the move is non-fatal (the preview
still works if it fails).

Covered by ReprocessorTest::test_reprocess_renames_valid_zip_to_elpx and
verified end-to-end via WP-CLI (a reprocessed .zip becomes .elpx and is
editor-accepted). Line coverage 79.55%.
CI's WPCS flagged two issues the local container's phpcs missed:
- Generic.Commenting.DocComment.LongNotCapital: the doc long description started
  with the lowercase brand 'eXeLearning'; reworded to start with a capital.
- Use the correct ignore code WordPress.WP.AlternativeFunctions.rename_rename
  (not file_system_operations_rename) for the in-place rename().
@erseco erseco merged commit eb8fb92 into main Jun 2, 2026
4 checks passed
@erseco erseco deleted the feature/reprocess-existing-elpx branch June 2, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a way to reprocess existing .elpx media attachments

1 participant