Developers

Contributing

Hubzero is an open source project with contributions from many groups and organizations. This book explains how to work on the code in a way that lands cleanly: the conventions the codebase follows, how commits are written, how to run the tests, and how to send a change back.

Reporting problems

Report bugs and security issues at https://help.hubzero.org/support.

Contributions

How a change gets from your working copy into Hubzero. The short version: fork hubzero/hubzero-cms, branch from the release line you are fixing, open a pull request, and say in it what broke and how you tested the fix.

The repository

The CMS is one repository, hubzero/hubzero-cms, with two top-level trees:

Tree What it holds
core/ The platform: components, plugins, modules, templates, the Hubzero framework library, migrations, and the muse command-line tool
app/ A hub's own overrides and configuration. Git-ignored; it is a running hub's local state, not shipped code

A contribution is a change under core/, docs/, tools/ or .github/. Nothing you write under app/ reaches the repository.

Source files carry an MIT header:

/**
 * @package    hubzero-cms
 * @copyright  Copyright © 2026 Purdue University. All Rights Reserved.
 * @license    http://opensource.org/licenses/MIT MIT
 */

A new file gets the current year. A file you modify keeps the earliest year already in its header and extends the range: Copyright © 2015-2026 Purdue University. All Rights Reserved. Do not change the @license line. The documentation under docs/ is MIT as well.

Most of core still reads Copyright (c) 2005-2020 The Regents of the University of California., from an earlier stewardship of the project. Leave those lines as they are; the change of holder applies to new work.

The root LICENSE file and core/composer.json both say MIT, and they agree with the header above. They did not until recently: the root file was the GNU General Public License version 2 and the manifest declared GPL-2.0-or-later, while every source file said MIT. The project settled on MIT and the two outliers were corrected.

Two files outside core/vendor/ remain third-party and stay under the GNU General Public License, because relicensing someone else's work is not the project's to do. Both are named at the foot of LICENSE, and their notices live in the files themselves:

File Origin
core/components/com_wiki/helpers/sanitizer.php The MediaWiki XHTML sanitizer, GPL version 2 or later
core/plugins/user/domainrestriction/helpers/IPv6Net.php Copyright (c) 2011 Juergen Enge, GPL version 2

Everything under core/vendor/ is installed by Composer and carries whatever license its package declares.

Third-party code the CMS includes or derives from is listed in ACKNOWLEDGMENTS.md, which also lists the contributors.

Branches

Each release line has a X.Y-main branch. The repository carries them back to 1.0-main; the recent ones are 2.2-main, 2.3-main and 2.4-main. 2.4-main is the current line and the repository's default branch. Point releases are tagged from it.

Branch your work from the release line the fix belongs to, and open the pull request against that same branch. Give the branch a name that means something to you; nothing depends on it.

There are also X.Y-dev branches — 2.4-dev, 2.5-dev, 3.0-dev — carrying work for lines that are not released. Unless a maintainer asks you to target one, send a fix to 2.4-main: a change that lands on a -dev branch reaches nobody's hub until that line ships.

Before you open a pull request

Install the development dependencies once:

(cd core && php bin/composer install)

Then:

(cd core && vendor/bin/parallel-lint --exclude vendor .)
(cd core && vendor/bin/phpunit -c phpunit.xml.dist)
php tools/lint/missing-facade-imports.php

Read PHP Coding Style for what each of those catches and how to run phpcs usefully against a codebase that has no committed ruleset. The suite takes about half a minute; Testing covers running one test while you work on it.

If you touched anything under docs/, rebuild the site and commit the result:

sh tools/docs/rebuild.sh

What the build checks

Three workflows run on a pull request, each on the paths it cares about. A pull request that touches only docs/ runs the documentation one and nothing else.

Workflow Runs when Checks
php-lint.yml any *.php changes php -l on every *.php under core and app outside vendor, then tools/lint/missing-facade-imports.php, then tools/lint/undefined-language-keys.php against a ceiling
tests.yml core/**.php, core/phpunit.xml.dist or the Composer files change Installs the Composer dependencies and runs vendor/bin/phpunit -c phpunit.xml.dist
pages.yml docs/, gh-pages/ or tools/docs/ changes Runs the documentation builder's tests, regenerates docs/reference and fails if it differs, builds the site, checks every internal link, and fails if the committed gh-pages/public is stale

A fourth, dev-push.yml, deploys to a Purdue development host and is switched off.

The facade check is worth understanding before it fails on you. The CMS registers Route, User, Lang and the rest as root-namespace aliases, so an unqualified Route::url() inside a namespaced file resolves to Current\Namespace\Route and fatals when that line runs. The file parses, so php -l says nothing, and the fault only surfaces on the path that reaches it. 730 of these were fixed at once; the linter keeps them from coming back. Run it with --fix to insert the missing use statements.

The language-key check is graded, not absolute. Lang::txt() prints the key when nothing defines it, and 444 keys in the tree are undefined today, so the workflow runs the linter with --max=444: the build passes at today's number and fails the moment your change adds one. Define the keys your change asks for. If you fix some existing ones, lower the ceiling in the workflow in the same pull request, so the count cannot creep back.

No workflow runs phpcs. Style is caught in review; see When the linter and the house style disagree.

The pull request

The pull request template asks for:

  • links to the issue or ticket the change answers
  • a summary of the problem in your own words
  • a summary of what the change does
  • what you specifically did to test it — the part reviewers most often have to ask for
  • whether the change needs to reach a production hub before the next core rollout
  • a named reviewer

CODEOWNERS is a single line, * @nkissebe, so every pull request automatically requests a review from the repository's maintainer, whatever it touches. Whether GitHub then blocks the merge until that review arrives is branch protection, which is a repository setting and not a file you can read here. Write for the review either way: the template's testing question is the one reviewers most often have to send a pull request back for.

Write the commit messages the way Commit Messages describes: an extension prefix, a sentence, and a body that says why.

What makes a change acceptable

Beyond the conventions:

  • It works, and you say how you know. A description of what you tested is worth more than an assertion that you did.
  • It is in English. Variables, functions, and comments. Code the maintainers cannot read is code they cannot maintain.
  • It is finished. If the feature needs language strings, a migration, or a documentation page, they come with it, not later.
  • It is one thing. A behaviour change and a reformatting pass are two commits, and often two pull requests. A reviewer cannot see one through the other.
  • It does not widen access by accident. A change to a permission check, an ACL rule, or a query's WHERE clause needs to say what it now allows that it did not before.
  • It is portable. Relative paths, configuration values, and hostnames — no absolute paths from your machine and no IP addresses.

Before proposing a feature rather than a fix, consider:

  1. Who is it useful to, and can it be switched off by everyone else?
  2. Is the target audience a hub visitor, a hub manager, or a system administrator, and is the interface right for them?
  3. What does it cost to maintain?
  4. Does something similar already exist that could be extended instead?

Reporting problems

  • Bugs and feature requests: https://help.hubzero.org/support, or a GitHub issue using the bug report template, which asks for the Hubzero version, the PHP version, the operating system, and steps to reproduce.
  • Security issues: email support@hubzero.org. Do not open a public issue. See SECURITY.md.

Some faults come from a Composer package rather than from Hubzero. When that happens the maintainers will usually work around it here and point you at the package's own tracker.

A development environment

You need a working hub to develop against; the CMS does not run standalone. Development Environment covers getting one. Installing a hub from scratch is being rewritten for the new web installer; see Installing a hub. The platform targets PHP 8.2.

Once the hub runs, replace its CMS directory with your clone, restore the app/ directory from the original, install the Composer dependencies, and run the migrations:

(cd core && php bin/composer install)
php core/bin/muse migration -f

muse migration runs in dry-run mode by default and lists what it would do; -f is the full run. Older instructions pair -f with -i. That flag is deprecated: it now behaves as -a, which only widens what is listed.

muse is the CMS command line; it does a great deal more than migrations.

Working on the documentation

The documentation is Markdown under docs/ in the hubzero-cms repository, built into the site you are reading by the scripts under gh-pages/. It is edited the same way as the code: on a branch, in a pull request, with the site rebuilt and checked before it merges.

Editing a page

Every page has an Edit this page on GitHub link in its footer. For anything larger than a typo, clone the repository and work locally:

python3 -m pip install -r gh-pages/requirements.txt -r tools/docs/requirements.txt pytest
sh tools/docs/rebuild.sh
python3 -m http.server -d gh-pages/public 8000

Then open http://localhost:8000/. Rebuild after each change; the build takes a few seconds.

The writing guide covers file naming, the metadata header, links, code blocks, callouts, and house style.

Reviewing an imported page

Most pages were imported from help.hubzero.org and carry a banner until someone checks them against the code. To review one:

  1. Read the page against the current code on 2.4-main: the controllers, views, and config.xml of the component it describes. Fix or rewrite what has drifted. Replace pasted code with an include directive that pulls the real file.
  2. Regenerate any screenshots from a current hub.
  3. Change the header to status: reviewed, add reviewed-against with the branch and commit you checked, and reviewed with the date.
  4. Rebuild, commit gh-pages/public/ with the change, and open a pull request.

The status page shows what is left in each book.

Committing

Commit gh-pages/public/ together with the source change. The Pages workflow rebuilds the site from source on every push and fails if the committed copy does not match, so a stale copy cannot be deployed by accident.

Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.