Migracio de dades

LearnDash to Moodle Migration

Suite of tools to migrate training content from WordPress/LearnDash to Moodle with XML parsing, HTML cleaning, catalog and visual remapping.

Context

Two WordPress sites with LearnDash contained hundreds of courses, thousands of users and years of accumulated progress that needed to be migrated to Moodle. The content was stored in proprietary formats (Elementor JSON, PHP serialized metadata) that were not directly portable.

What it does

6-phase pipeline with intermediate files (JSON/CSV) as the source of truth:

  1. Extraction: WXR XML parsing, extraction of Elementor content from _elementor_data, HTML cleaning (removal of scripts, styles, data-* attributes)
  2. Catalog: Unified JSON with ordered course-lesson relationships, automatic remapping proposal by similarity (SequenceMatcher)
  3. Review: Web interface on port 8765 with search, assignment, drag-and-drop of lessons, inline WYSIWYG editor
  4. Resources: Detection and download of PDFs, images, audios, videos with CSV manifest
  5. Users: Consolidation by email from multiple WordPress sites, identity merging
  6. Import: Moodle plugin (local_ldmigrate) with idempotent external API to create pages and resources

Main difficulties

  • Elementor content stored as serialized JSON in metadata, not as rendered HTML
  • HTML full of data- attributes* and proprietary non-portable classes
  • Scattered course-lesson relationships in PHP serialized metadata with undocumented formats
  • Numeric IDs colliding between multiple sites
  • URLs with special characters (accents, encoded spaces) that failed during downloads
  • LearnDash progress serialized as JSON in wp_usermeta

Solutions

  • Custom Elementor JSON parser: recursive walk of known widgets toward semantic HTML
  • Systematic regex: removal of scripts/styles/noscript, data-/aria-/elementor-* attributes, preserving href/src/alt/title
  • PHP serialized metadata parser to extract ordered lesson IDs
  • source_user_key with site prefix to disambiguate IDs between sites
  • request_url(): IDNA domain normalization + selective URL-encoding
  • Moodle plugin with idempotency by idnumber: avoids duplicates on re-executions

Lessons learned

  • Automate the 90% that is mechanical and leave the screen/CSV for the 10% that is ambiguous
  • Intermediate files (JSON/CSV) as the source of truth allow pausing and resuming the migration
  • Zero external dependencies (only Python stdlib) simplifies deployment and avoids version conflicts
  • A local Moodle plugin with external API is safer than manipulating the DB directly