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.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.

