Skip to content

com_courses: fix outline editor exhausting memory on newer courses - #1920

Open
denphi wants to merge 1 commit into
hubzero:2.4-mainfrom
denphi:fix/com-courses-outline-offering-id-513503
Open

com_courses: fix outline editor exhausting memory on newer courses#1920
denphi wants to merge 1 commit into
hubzero:2.4-mainfrom
denphi:fix/com-courses-outline-offering-id-513503

Conversation

@denphi

@denphi denphi commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Section::access() wrote the section's own id into the shared permissions offering_id, so Permissions::_calculate() then asked Course::offering() for an offering whose id is really a section id. When no offering row carries that id, Course::offering() discards the correctly-cached offering and caches an id-less one in its place -- Tables\Offering::load() ignores course_id for a numeric id, so it simply finds nothing.

Every later $course->offering() returned that empty model, so Offering::units() passed offering_id => 0 to the units query. The _buildQuery() guards read a falsy scope id as "no filter" rather than "match nothing", dropping the WHERE clause entirely, so the query returned every unit on the hub -- then every asset group, then every asset. That is ~14,500 asset models, each with its own table object and SHOW FULL COLUMNS round trip, exhausting a 256MB limit in about six seconds.

Only the editasset/editwiki/edittool layouts die; the outline builder survives because it reads units before the permissions calculation has poisoned the cached offering.

Courses whose section id happens to equal their offering id are unaffected, which is why this only shows up on courses recent enough for the two id sequences to have drifted apart.

Corrects the offering_id, and hardens the three scope-id guards so a zero can no longer widen a scoped query into a full table scan. admin/controllers/assets.php passed 0 to mean "every scope", so it now omits the key rather than relying on that ambiguity.

Also guards edittool.php against calling files() on a handler that does not implement it -- a url/link asset reaches that layout with a Models\Assets\Url handler and fatals.

Ticket: https://nanohub.org/support/ticket/513503

Section::access() wrote the section's own id into the shared permissions
`offering_id`, so Permissions::_calculate() then asked Course::offering()
for an offering whose id is really a section id. When no offering row
carries that id, Course::offering() discards the correctly-cached
offering and caches an id-less one in its place -- Tables\Offering::load()
ignores course_id for a numeric id, so it simply finds nothing.

Every later $course->offering() returned that empty model, so
Offering::units() passed offering_id => 0 to the units query. The
_buildQuery() guards read a falsy scope id as "no filter" rather than
"match nothing", dropping the WHERE clause entirely, so the query
returned every unit on the hub -- then every asset group, then every
asset. That is ~14,500 asset models, each with its own table object and
SHOW FULL COLUMNS round trip, exhausting a 256MB limit in about six
seconds.

Only the editasset/editwiki/edittool layouts die; the outline builder
survives because it reads units before the permissions calculation has
poisoned the cached offering.

Courses whose section id happens to equal their offering id are
unaffected, which is why this only shows up on courses recent enough for
the two id sequences to have drifted apart.

Corrects the offering_id, and hardens the three scope-id guards so a
zero can no longer widen a scoped query into a full table scan.
admin/controllers/assets.php passed 0 to mean "every scope", so it now
omits the key rather than relying on that ambiguity.

Also guards edittool.php against calling files() on a handler that does
not implement it -- a url/link asset reaches that layout with a
Models\Assets\Url handler and fatals.

Ticket: https://nanohub.org/support/ticket/513503

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@denphi
denphi requested a review from nkissebe as a code owner August 26, 2026 00:39
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.

1 participant