Stedica LMS — changelog
=======================

WordPress.org truncates readme.txt's changelog section, so readme.txt carries the most
recent releases and this file carries all of them, including those. Both ship in the plugin.

This file begins at 2.2.3. Releases before that are not recorded anywhere in the package;
readme.txt used to promise a history "from 1.0.0 onward" that has never been here.

= 2.5.9 =
**Two things that were quietly losing data: a personal-data export and deleted courses**

* **A personal-data export was leaving out points, streaks and levels.** The export walks
  each of its tables a page at a time, and every page was ordered by a column called `id`.
  One of those tables does not have one — points are keyed by the learner, one row each —
  so the database refused that query every time it ran, and the export finished, and
  downloaded, with that section simply absent. A learner who asked what you hold on them
  was told less than the truth, while the erasure path had been removing it correctly all
  along. Nothing reported a problem: the file arrived, the section was gone. Each table now
  states the column it is ordered by, and a test refuses any that names a column its table
  does not have.
* **Deleting a course now deletes its SCORM and H5P packages too.** It never did. The
  course row went, the uploaded archive stayed on disk for good — one file per import, and
  unreadable from the moment the course was gone, because access to it is granted by
  enrolment in that course. On update, packages left behind by courses deleted earlier are
  removed once. Course artwork is deliberately left alone: its addresses may already be
  published in a page or an email, and those keep working.

= 2.5.8 =
**Spanish, and the plugin's home screen now carries the Stedica banner**

* **Stedica LMS is now in Spanish.** Spain and Mexico, 817 strings covering the admin
  screens and everything a learner sees. The translations were finished before 2.5.7 but
  did not reach the release, so a Spanish site has been running an English interface with
  a complete translation sitting unused in the package.
* **Fixed: a third of the Spanish translation could never load.** The catalogue for the
  main admin bundle — 358 of the 817 strings, which is most of what an instructor reads —
  was stored under a filename WordPress does not look for. Nothing reported an error: the
  package was complete and valid, and the screens were in English regardless. The naming
  is now derived from the script's own path, and a test fails if it ever drifts again.
* **The home screen opens with the same banner as the other Stedica plugins** — the brand
  mark, what the plugin does, and a row of what is included, over the shortcuts to
  Courses, the grading queue and Settings. The shortcuts follow what you are allowed to
  do, so an instructor is not offered Settings and a learner is not offered either.
* The plugin's own stylesheet now declares its dependency on Dashicons rather than relying
  on WordPress having loaded it for something else.
* **The licensing library was updated to Freemius SDK 2.13.5**, which Freemius released as
  a security fix. It adds output escaping to the account and activation notices — the
  places where an email address or a message returned by their API is printed back to you —
  and it now refuses to run the opt-in for a user who cannot activate plugins. That library
  owns the opt-in screen, the account page and the update channel in this plugin, so the
  fix applies to screens you actually see rather than to dormant code. Nothing in it is
  modified; the version and the tarball checksum are recorded in `vendor/freemius/`.

= 2.5.7 =
**SCORM packages are no longer unpacked on the server, and three database queries were rewritten**

* **A SCORM package now stays as the file you uploaded.** Importing one used to unpack it,
  writing its pages and scripts out as hundreds of separate files. It is now kept as the
  single archive you uploaded and read from directly when a learner opens the course.
  Nothing about the course changes for you or for them — the same modules play, resume
  where they left off, and audio and video still seek — but the server stores one file
  instead of hundreds, and a 12MB course takes 12MB rather than 24MB. Packages imported
  before this release keep working.
* **The certificate design moved into its own stylesheet.** It was written inside the
  certificate template; it is now `certificate.css` beside it. A theme that overrides the
  certificate gets its own copy to edit, which was not possible before.
* **Three course-lookup queries were rewritten** so that the way they pass values to the
  database is evident at a glance rather than hidden behind a helper. No behaviour changes.

= 2.5.6 =
**SCORM packages and certificate downloads were broken for everyone, and a package of security fixes**

* **SCORM courses and certificate downloads did not work at all.** Every protected file —
  a SCORM package, a certificate PDF, a learner's own uploaded coursework — was served
  through an address that refused the browser's own request, so learners saw an empty
  course player and a download button that did nothing. The cause was that these
  addresses are loaded by the browser directly, and the plugin was checking them as
  though they had come from its own JavaScript. Fixed, and covered by a test that fails
  if it ever returns.
* **SCORM packages now load their own images, audio and scripts.** On sites using Plain
  permalinks the package's address could not be used to find the files alongside it, so
  the first page appeared and everything it referenced was missing. Importing a package
  on such a site now also recommends switching permalinks, which removes the last case
  where a host could refuse the new address.
* **A malformed SCORM package could fill the disk.** The size limit trusted the figures
  the package declared about itself, and those can be forged: an archive claiming to be
  one kilobyte could write five megabytes, and a one-megabyte upload could write tens of
  gigabytes. The limit is now enforced against compressed size and against what actually
  lands on disk.
* **A SCORM package could smuggle in server-executable or server-configuration files.**
  The filter only examined the end of each filename, so names like `course.php.jpg`,
  `php.ini`, a trailing-space `.htaccess ` — which Windows silently turns into a real
  one — and handler types the list had simply omitted all passed. Every part of every
  path is now checked, and legitimate multi-language filenames such as
  `messages.pl.json` are still accepted.
* **A failed import no longer leaves the package behind.** Importing anything that was
  not a valid SCORM package extracted it in full and then abandoned the files on disk,
  with nothing recording them and nothing cleaning them up. Uploaded packages are also
  no longer staged in a publicly readable folder under the uploader's own filename.
* **Duplicating a course no longer produces a dead SCORM topic.** The copy pointed at the
  original course's package, which its own learners have no access to, so the topic
  silently failed for all of them. The copy now starts with an empty SCORM topic for you
  to re-import into.
* **The plugin stopped writing inside its own folder.** The PDF library cached font data
  there, which WordPress deletes on every update and which plugins are not permitted to
  write to. It now goes in the uploads folder, where it survives updates.
* **Certificates are no longer written as web pages when no PDF renderer is present.** A
  site without the PDF library had an HTML file stored in its place; those files are now
  removed on update and no new ones are written.
* **Admin notices are confined to this plugin's own screens.** A notice about gradebook
  figures appeared on every screen in the dashboard, its dismiss button only lasted until
  the next page, and whichever instructor dismissed it silenced it for everyone. It is
  now on the plugin's own screens, dismissed per person, and stays dismissed. A notice
  about licensing was removed entirely and another is limited to the plugins screen.
* **The uploads folder is now named for the plugin.** Files were kept in
  `wp-content/uploads/stedica/`; they are now in `wp-content/uploads/stedica-lms/`, which
  is what the WordPress.org guidelines require. Existing certificates, submissions and
  SCORM packages are moved automatically on update and keep working. Course artwork is
  copied rather than moved, so any image address already published elsewhere — in a page,
  an email, another system — keeps resolving from the old location.
* **An instructor could import a SCORM package into another instructor's course.**
  Uploading a package checked only that you may manage courses, not that the course was
  yours, and the course id decides who is allowed to play the package. It is now checked,
  and a course id that does not exist is refused instead of extracting to a folder nothing
  can serve.
* **The "Visit plugin site" link went to a page that did not exist.**

= 2.5.5 =
**A security fix for uploaded coursework, and the plugin stops describing features it does not have**

* **Submitted coursework could be reachable by guessing a web address.** Files a learner
  uploaded were kept in a folder named only by a sequence number, so an address ending
  submissions/3/essay.pdf was guessable. Permission is checked whenever WordPress handles
  the request — but on a server that ignores .htaccess, which includes nginx and some
  Apache setups, the web server answers first and WordPress never runs. Submissions now
  sit behind an unguessable folder name derived from your site's own secret keys. Files
  uploaded before this release keep working and nothing has to be moved.
* **Course content is cleaned on every path that saves it, not only in the editor.**
  Importing a course from LearnDash wrote its lesson content straight to the database.
  Rich-text lessons keep their formatting exactly as before; what is new is that a maths
  or programming question containing a "<" is not damaged by the cleaning itself.
* **Editing a question in the bank is now checked the way creating one is.** An edit could
  save a question with no text, or a type nothing knows how to mark — leaving a question
  that could neither be answered nor found again to repair.
* **The plugin no longer names add-on features as though they were included here.** The
  dashboard introduced it as a platform with learning paths, live sessions and
  competencies; the sample course pointed at Analytics, which is an add-on screen; and the
  suggested privacy text mentioned live session attendance. None of those are in this
  download. The developer README, which is not documentation for you, no longer ships
  inside the plugin either.

* **A Settings toggle that could not be saved is gone.** "Warn a learner before their
  certificate expires" was listed among the automatic emails and this plugin has no such
  email — that one belongs to the add-on. Unticking it appeared to work, said "Settings
  saved", and the box was ticked again on the next page load.
* **Three events in the Audit Log showed a raw name instead of a sentence.** Enrollment
  restored, enrollment access changed and licence activated now read like every other row.
* **A data export now includes records left behind by an add-on you have removed.** They
  were already erased on request and removed on uninstall; they were missing from the
  export, so a subject access request returned less than an erasure removed.
* **"Remove all data on uninstall" now removes all of it.** Tables created by an add-on
  that is no longer installed survived, including ones holding personal data.
* **The upload protection files now work on multisite and on sites that moved their
  uploads directory.** The rules were written for the default layout and matched nothing
  on either, so the webserver-level guard in front of submitted work was not in effect.
* Fixed: a malformed request could set the audit log's retention to one day.
* Removed a rewrite-rules flush on activation and deactivation. This plugin adds no
  rewrite rules, so it was rebuilding every other plugin's for no reason.

= 2.5.2 =
**Two things that looked broken and were not**

* **The "try the premium features" notice no longer comes back.** Dismissing it used to
  be temporary — it reappeared on every admin page 30 days later, and again 30 days after
  that. Dismissed now means dismissed. The Upgrade menu item is still there if you want it.
* **The instructor dashboard no longer shows a row of blank tiles.** Active learners,
  Completed, At risk, Upcoming sessions and Quiz pass rate all read as em dashes or zero,
  because they are drawn from the paid add-on's data and this plugin does not have it. A
  counter permanently reading zero looks broken rather than absent, so those tiles are now
  simply not shown. Awaiting grading, which is this plugin's own, still is.

= 2.5.1 =
**Fixes found by a full pre-submission audit**

* **Fixed: completing a lesson could fail.** The code that recalculates course progress
  had been moved out of this plugin by mistake while the paid features were being split
  out, but the three places that call it stayed. Finishing a lesson or having a quiz
  graded hit an error. This is the important one in this release.
* **At-risk scoring has moved to the add-on, where it belongs.** Three hundred lines of
  it shipped in this download with nothing here using it. The feature was always part of
  the paid analytics; only the code was in the wrong package.
* **The Stripe credential fields are gone from Settings.** This plugin sells through
  WooCommerce and contains no card-processing code, so it was offering two fields for an
  engine it does not have — and then refusing to save them. The add-on now provides both.
  A key stored earlier can still be removed.
* **The licence screen said the paid features were "switched off".** They are not switched
  off; they are simply not installed. It now says so. It also said "seven" where there are
  eight.
* Corrected the readme: a shortcode was documented that does not exist, and a line told
  you to install a PDF library that already ships inside the plugin.

= 2.5.0 =

**The paid add-on now installs beside this plugin instead of replacing it**

Nothing in this download changes what it can do. What changed is how the paid half
arrives: it is a second plugin that runs alongside this one, rather than a package that
replaced it. This plugin stays installed, keeps updating from WordPress.org and keeps
holding the licence for both halves.

* **Linking a WooCommerce product to a course is no longer restricted.** The fulfilment
  hooks, the orders ledger and the admin screen were all here and all working, but saving
  a NEW product-to-course link was refused unless the paid add-on was installed, with an
  upsell printed beside the control it had just disabled. A built-in feature limited by a
  licence is exactly what the directory guidelines forbid, and it contradicted this
  plugin's own description. The limit is gone; the feature was always here.
* **Fixed: the plugin's own screens could be replaced by a "connect your account" prompt.**
  The licensing SDK sat in activation mode until the tracking opt-in was accepted, and
  rendered its own screen in place of the course list. A plugin from the directory must
  never require an account before it works. The opt-in is now a dismissible notice.
* **Fixed: updating this plugin could deactivate it.** On a site with the paid add-on and
  a licence, the SDK treated this plugin's folder as the wrong one and rewrote the active
  plugin list during an update — silently removing the plugin that holds the licence and
  the LMS itself, with nothing on screen to explain it.
* **Fixed: the paid add-on's update could be offered for this plugin.** On a scheduled
  update check the SDK filed its download against the wrong plugin, so WordPress would
  have installed the paid package over this one.
* Both fixes live in the add-on, not here. This download contains no code that touches
  WordPress's update routines, which is what the directory requires.

= 2.4.0 =

**The plugin is now two packages, and free can sell**

Everything below is free-tier behaviour. The paid areas — AI, live sessions, competencies,
learning paths, compliance, webhooks, native Stripe checkout with subscriptions, plans and
coupons, and the business analytics — now live in a separate add-on and are simply not in
this download. That is what the WordPress.org guidelines ask for: an absent feature rather
than a disabled one.

* **Selling through WooCommerce works.** It did not. The provider setting was withheld, so
  Commerce never bound its hooks and every purchase enrolled nobody, silently. Separately,
  a free site could not set a course price at all — the field was drawn and the value
  discarded — so the paywall never engaged and every course gave itself away. Both fixed,
  and a priced course now links to the shop entry that sells it.
* **Linking a WooCommerce product to a course is no longer plan-gated.** The fulfilment
  hooks, the orders ledger and the admin screen were all here and all working, but saving
  a NEW product-to-course link was refused unless the paid package was installed, with an
  upsell printed beside the control it had just disabled. That is a built-in feature
  limited by a licence, which the guidelines forbid outright, and it contradicted this
  plugin's own description. The limit is gone; the feature was already here.
* **Fixed: a site that stopped paying could white-screen.** Commerce called the Stripe
  event handler by name whenever the stored provider said "stripe". Remove the add-on and
  that is a fatal on every request, which is exactly what a downgrade looks like.
* **Per-course progress reporting and CSV export are free, and no longer plan-gated.**
* **Fixed: the setup checklist read a paid endpoint.** The first screen a new administrator
  sees took its counts from the analytics payload, so without that add-on the checklist
  never completed and the panel never went away. It has its own endpoint now.
* **Fixed: a membership bought through WooCommerce could take money and grant nothing.**
* **The orders ledger stays free.** It is the record of money the site took, and a site
  owner needs it whether or not they still have the add-on that sold the membership.
* Completion records, subscriptions and other add-on tables are never dropped when an
  add-on is removed, remain reachable for a data export or erasure request, and are only
  destroyed if the administrator explicitly asks for all data to be removed on uninstall.

= 2.3.0 =

**Groundwork for the free and premium split, and two fixes worth having on their own**

* **The upgrade button went to a page that does not render.** Every "upgrade" and "start the
  free trial" control resolved to the licensing SDK's own in-admin pricing screen, which is a
  React app that fetches its plans over admin-ajax and came up blank on two of our production
  sites. They now go to the hosted checkout, which is the page that actually takes money.
* **The free trial had no way in.** Nothing in the plugin ever linked to the trial flow, while
  the dashboard link read "start the free trial" and pointed at a pricing page that does not
  start one. It does now, and the trial is 7 days with no payment method.
* **The completion register's expiry backfill had never once run.** It joined a column the
  certificates table has never had, failed on every install since 2.1.0, and then recorded
  itself as complete anyway — so certificate holders from before 2.0 kept a blank expiry date,
  and the recertification reminders never saw them. Fixed, and it retries on sites that
  recorded the earlier false success.
* Internally, feature areas are now decided by what is installed rather than by what has been
  paid for. No behaviour changes in this release; it is the foundation for offering a free
  edition on WordPress.org with the paid areas as a separate add-on.

= 2.2.3 =

**One corner radius across the admin screens**

* Panels, cards and buttons on the Stedica LMS screens now share a single 6px corner radius.
  The course builder alone had three — lesson cards at 6px, topic cards, quiz settings,
  question cards and rubrics at 4px, and a couple of rows at 3px — which, nested inside one
  another, read as three different components rather than one editor. Buttons are included,
  scoped to our screens so the rest of wp-admin is untouched.
* The risk pill and the circular question number keep their own shapes; they are not
  rounded corners.

