- Root entry points live in
index.phpandadmin/for the administration console UI./loginand/logout(and the legacylogin.php/logout.phpURLs) are Login/Logout controllers viatsugi.php. - Core feature areas are organized by domain:
api/(HTTP endpoints),lti/(LTI launch and flow),mod/(optional modules),tool/(in-tree tools shipped with this repo),store/(tool store),util/(shared helpers), andlocale/(translations). - Configuration starts from
config-dist.php; copy toconfig.phpand edit for local secrets and settings. - Dependencies are vendored in
vendor/(includingtsugi/libandkoseu/lib), so changes there should be intentional and tracked.
vendor/is committed to git on purpose: production deploys do not run Composer, which avoids deploy-time network/hiccups and keeps releases reproducible. Any dependency bump must updatecomposer.json,composer.lock, and thevendor/tree together in the same change.- Dev packages are gitignored.
.gitignoreexcludesvendor/phpunit/,vendor/phpstan/,vendor/myclabs/,vendor/symfony/panther/, and otherrequire-devtrees. Production checkouts only get what git tracks. - Always finish a vendor commit with
composer run finalize-vendor(runscomposer install --no-devwhen needed, thenqa/pre-commit-vendor-check.sh). This also runs automatically aspost-update-cmdaftercomposer update/composer require, but agents must run it explicitly when unsure. - Install git hooks after a fresh checkout:
bash qa/install-git-hooks.sh(see.cursor/rules/git-hooks-and-vendor.mdc). Agents must verify the hook exists at session start. - See
docs/composer.mdfor day-to-day commands. Common patterns:composer update <package> --ignore-platform-reqs -W --no-dev— bump one direct dependency and its transitive updates.composer install --no-dev --ignore-platform-reqs— required last step before committingvendor/; regenerates autoload without dev references.composer audit— check for security advisories before/after updates.
platform-checkis false and the PHP constraint is>=8.4.0; do not raise the PHP version unless explicitly asked. Use--ignore-platform-reqslocally if your CLI PHP is newer (e.g. 8.5) than a package’s declared support.- Pinning strategy: security-sensitive packages (
phpseclib,firebase/php-jwt,htmlpurifier) and patched Symfony CVE fixes use exact versions incomposer.jsonsocomposer updatecannot accidentally jump Symfony 8.0 → 8.1 or similar. Other libraries use>=floors; the lockfile is the real pin. - Symfony is mixed 7.4 + 8.0 (e.g.
http-foundation/http-kernelon 7.4.x,routing/mimeon 8.0.x). That is intentional. When advancing Symfony, stay on patch releases within the current minor (e.g. 7.4.12, 8.0.13)—do not bump to 8.1 unless planned. Pinerror-handlerandvar-dumperto 7.4.x when updatinghttp-kernelor Composer may pull 8.1. - Do not remove direct dependencies (e.g.
symfony/browser-kit,symfony/routing) even if core Tsugi code does not import them; optional modules and tools rely on them. - Deferred / coordinated upgrades (do not batch casually):
firebase/php-jwt7.x (requiresgoogle/auth^1.50 andgoogle/apiclient^2.19; test LTI 1.3 launches),minishlink/web-push10.x, Symfony 8.1+, andsymfony/process8.x. Track these withcomposer auditand plan explicit upgrade PRs. lib/composer.jsonvs rootcomposer.json:lib/is the tsugi/lib library tree (also vendored at the root). Keeplib/composer.jsonin sync with rootcomposer.jsonas much as possible for shared direct dependencies—especially security-sensitive pins (phpseclib,firebase/php-jwt,htmlpurifier) and the PHP constraint. When you bump or pin a package in one manifest, update the other if that package appears in both. Root-only deps (Symfony, Google, etc.) stay in rootcomposer.jsononly;lib/composer.lockis gitignored and used for local lib development.
bash qa/install-git-hooks.sh— install the local pre-commit hook (not automatic on clone; Cursor agents should verify it exists at session start).bash qa/pre-commit-vendor-check.sh— sanity-check that committed autoload does not reference gitignored dev packages.docker compose build— build the local Docker image.docker compose up— start the app and initialize the database; default URL ishttp://localhost:8888/tsugi.composer update tsugi/lib— update the Tsugi PHP runtime without bumping all transitive dependencies (seedocs/composer.md).composer update --ignore-platform-reqs— advance dependencies when explicitly needed.
isLoggedIn()means the current user id is non-zero (loggedInUserId() !== 0). It does not require a non-zerocurrentContextId()(course/context). Users can be authenticated site-wide without a course.- Contexts and LTI: The long-term model is that users log in first, then pick among multiple contexts (courses). For now, there is typically one active context at a time unless the request is an LTI launch (where the LMS may bind a course). LTI launches can also be “no context” (system-wide or non-course tools), so “logged in” must remain valid when
currentContextId()is zero. - Use
currentContextId()(and checks likecurrentContextId() !== 0) only when the code path requires a course/context. Do not changeisLoggedIn()to require context, and do not “fix” call sites by folding context into login unless the product requirement explicitly asks for it. loggedInUserId()andcurrentContextId()are paired by source (session vs$USER/$CONTEXT); see the docblocks inlms_lib.phpbefore refactoring auth checks.
- Follow the existing PHP style in nearby files: 4-space indentation, braces on the same line, and compact, readable conditionals.
- Use established naming patterns (
$CFGfor config,Tsugi\namespaces for core classes) and keep filenames lowercase with hyphens/underscores as already used. - Prefer small, focused changes in
admin/,api/, orlti/that mirror current structure and routing patterns.
- This repository does not include a formal automated test suite;
test/old_test.phpis legacy. - Validate changes manually by running the app, visiting key admin screens, and re-running the database upgrade flow if schema changes are involved.
- Recent commits are short, sentence-style summaries (e.g., “Add item to headers.”). Keep messages concise and action-oriented.
- PRs go through GitHub and require a CLA before acceptance (see
CONTRIBUTING.md). - If you touch vendored libraries in
vendor/, include clear notes in the PR so maintainers can propagate changes across related repos.
- Treat
config.phpas local-only: never commit real secrets or production credentials. - Rotate any exposed keys immediately and update
config-dist.phponly with non-sensitive defaults.