Developers

Upgrade guide

Before 2.0 the platform used an older set of class names, and 2.0 replaced most of them. An extension written against JFactory, JText and JRequest still needs converting. This page is that translation table, plus what to do about the database when you upgrade a hub.

This page is for inherited code only. Read it when you have an old extension to bring forward, or when you are reading a file in this tree that still uses the old names — there are plenty. Do not use it as a menu. Nothing new should be written against the legacy names, and nothing new should reach into the container where a facade exists: write User::get('id'), not JFactory::getUser() and not App::get('user')->get('id'). The container call is correct and works; it is simply the long way round, and it is what the right-hand column of the table below is translating away from. Use App::get(...) only for the few services that have no facade — the database driver is the one you will meet — as Facades sets out.

The conversion is mechanical enough to do in an afternoon for a small extension. The order that wastes least time: rename the classes, add the use lines for every facade the file now names, run php -l over the result, then run php tools/lint/missing-facade-imports.php <your path> to catch the imports you missed. That last step is not optional — a missing import is the one mistake in this list that neither PHP nor your eyes will find.

For the Hubzero 1.x names that changed at the same time, see Release notes.

Directory structure

The tree has two top-level directories:

/app
/core
index.php

core/ is the platform — the framework library, the shipped components, plugins, modules and templates, and the migrations. app/ is one hub's own state: its configuration, its overrides, its logs, cache and uploads. index.php at the root is the only entry point. There is no administrator/ directory and no api/ directory; both applications are served from the same front controller.

Structure covers the layout in full.

Constants

Legacy name Hubzero
JPATH_ROOT, JPATH_BASE, JPATH_SITE PATH_ROOT
JPATH_ADMINISTRATOR Nothing. The directory does not exist.
JPATH_COMPONENT Component::path($option)
_JEXEC _HZEXEC_
n/a PATH_APPROOT/app, this hub's own data
n/a PATH_COREROOT/core, the framework and shipped extensions

When including a file from within the same extension, prefer PHP's own __DIR__ and __FILE__ over any constant:

// This file is ROOT/app/components/com_example/admin/example.php

// dirname(__DIR__) moves up one level, to com_example
require_once dirname(__DIR__) . DS . 'models' . DS . 'foo.php';
require_once __DIR__ . DS . 'controllers' . DS . 'example.php';

Better still, do not include anything. The class loader resolves Components\Example\Models\Foo to that path on its own; see Constants and Structure.

Classes

Most of the conversions are a facade with the J dropped. Facades are registered as root-namespace aliases, so a namespaced file must import the one it uses — use Route;. Leave the import out and the name resolves inside your own namespace instead, which usually still works by accident and fails outright on the API and CLI clients, in any namespace that already contains a class of that name, and in anything that runs before the application has loaded. See Facades.

JRoute

Legacy name Hubzero
JRoute::_($url) Route::url($url)

JText

Lang replaces JText, and _() and sprintf() merged into one Lang::txt() that takes a variable number of arguments. Pass more than one and the translator does the replacement.

// Language file
COM_EXAMPLE_HELLO="Hello!"
COM_EXAMPLE_HELLO_NAME="Hello, %s!"
// Outputs 'Hello!'
echo Lang::txt('COM_EXAMPLE_HELLO');

// Outputs 'Hello, Hubzero!'
echo Lang::txt('COM_EXAMPLE_HELLO_NAME', 'Hubzero');
Legacy name Hubzero
JText::_() Lang::txt()
JText::sprintf() Lang::txt()
JText::plural() Lang::txts()
JText::alt() Lang::alt()

JRequest

Every public JRequest method survives on the request object, so dropping the J is usually enough:

// Via the application container
$foo = App::get('request')->getVar('foo');

// Via the facade
$foo = Request::getVar('foo');
Legacy name Hubzero
JRequest::* Request::*

JToolbarHelper and JSubMenuHelper

Class name only; the methods and their arguments are unchanged.

// Legacy
JToolbarHelper::publishList();

// Hubzero
Toolbar::publishList();

JSubMenuHelper becomes Submenu, with one difference worth noticing: the link is routed.

Submenu::addEntry(
	Lang::txt('COM_COLLECTIONS_POSTS'),
	Route::url('index.php?option=com_collections&controller=posts'),
	$controllerName == 'posts'
);

JHtml

This one is not a rename. The legacy API passed everything through JHtml::_() with a dotted first argument naming the sub-library and the function. Hubzero makes the sub-library the method and the function the first argument:

// Legacy
echo JHtml::_('grid.sort', 'COM_COLLECTIONS_COL_TITLE', 'title', $dir, $sort);
echo JHtml::_('behavior.framework');

// Hubzero
echo Html::grid('sort', 'COM_COLLECTIONS_COL_TITLE', 'title', $dir, $sort);
echo Html::behavior('framework');

The sub-libraries are the classes in core/libraries/Hubzero/Html/Builder/: access, asset, batch, behavior, category, content, contentlanguage, grid, input, select, sliders, tabs. There is no date sub-library; for a relative date use Date::of($d)->relative().

Factory objects

Objects that came from JFactory come from the service container, and most have a facade in front of them. method() below stands for whatever you used to call on the legacy object:

// Legacy
$user = JFactory::getUser();
echo $user->get('name');

// Hubzero
echo User::get('name');
Legacy name Container Facade
JFactory::getDbo() App::get('db') n/a
JFactory::getUser(), JUser::getInstance() App::get('user') User::method()
JFactory::getSession() App::get('session') Session::method()
JFactory::getDocument() App::get('document') Document::method()
JFactory::getConfig() App::get('config') Config::method()
JFactory::getLanguage() App::get('lang') Lang::method()
JFactory::getCache() App::get('cache.store') Cache::method()
JFactory::getLogger() App::get('log')->logger('{name}') Log::method()

Dates

JDate becomes Hubzero\Utility\Date, reached through the Date facade. With no argument it means now, in UTC.

// The current UTC timestamp in the database's format: "2026-04-03 12:23:56"
echo Date::toSql();

// The current UTC timestamp as a Unix time
echo Date::toUnix();

// The current UTC year: "2026"
echo Date::format('Y');

// Adjusted to the hub's timezone. UTC 12:23 pm on an Eastern hub is "08:23 am"
echo Date::toLocal('g:i a');

Date::of() takes a specific timestamp, and an optional timezone that defaults to UTC:

echo Date::of('2013-08-12 17:01:34')->format('Y');    // "2013"
echo Date::of('2013-08-12 17:01:34')->toLocal('g:i a'); // "1:01 pm"

See Dates.

Users

Any method called statically on User, other than getInstance(), acts on the current user — the equivalent of JFactory::getUser()->method().

// Legacy
echo JFactory::getUser()->get('name');

// Hubzero
echo User::get('name');

getInstance() returns the object behind the facade, with or without an id or username:

$user  = User::getInstance();      // the current user
$other = User::getInstance(1234);  // JFactory::getUser(1234)

The database

An upgrade is not finished when the files are in place. Schema and data changes ship as migrations — small PHP classes under a migrations directory with an up() and a down() — and muse runs the ones a hub has not seen, recording each in #__migrations so it never runs twice.

php core/bin/muse migration      # dry run: lists what would happen
php core/bin/muse migration -f   # actually run them

The dry run is the default, which is the safest thing about the command and the easiest to miss. -e com_example limits the run to one extension, -d down reverses, and the full option list is in the muse migration reference.

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