Below is a focused recipe to replace the current two-level collapse toggles (“parent-categories” and “child-categories”) with an arbitrary number of “Category Level” toggles (up to 9). You’ll end up with checkboxes like:
File
protected/modules/abcdirectory/models/Configuration.php
Patch
Add at the top of the class (after existing constants):
/**
* Maximum number of nested category levels to expose for collapse toggles.
*/
public const MAX_CATEGORY_LEVELS = 9;
Replace your existing getSpaceBrowserCollapsedSectionOptions() entirely with:
public static function getSpaceBrowserCollapsedSectionOptions(): array
{
$options = [
self::SPACE_BROWSER_COLLAPSED_SECTION_MY_SPACES => Yii::t('AbcdirectoryModule.config', 'Spaces I am a member of'),
];
// dynamically add “Category Level 1” … “Category Level 9”
for ($lvl = 1; $lvl <= self::MAX_CATEGORY_LEVELS; $lvl++) {
$key = 'category-level-' . $lvl;
$label = Yii::t('AbcdirectoryModule.config', 'Category Level {n}', ['n' => $lvl]);
$options[$key] = $label;
}
// keep your other two:
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_UNCLASSIFIED_SPACES] = Yii::t('AbcdirectoryModule.config', 'Unclassified spaces');
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_OTHER_SPACES] = Yii::t('AbcdirectoryModule.config', 'Spaces I\'m not a member of');
return $options;
}
Remove the old constants for parent‐ and child‐categories:
- public const SPACE_BROWSER_COLLAPSED_SECTION_PARENT_CATEGORIES = 'parent-categories';
- public const SPACE_BROWSER_COLLAPSED_SECTION_CHILD_CATEGORIES = 'child-categories';
Your config form already has:
<?= $form->field($model, 'spaceBrowserCollapsedSections')
->checkboxList(Configuration::getSpaceBrowserCollapsedSectionOptions()) ?>
so once you regenerate the option list (step 1), you’ll see nine “Category Level” checkboxes instead of the old two.
Wherever you currently collapse/expand “parent-categories” and “child-categories” in your browser view (SpaceBrowser.php or similar), replace that with a loop over levels:
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$collapsed = $config->spaceBrowserCollapsedSections;
// for level i:
if (in_array("category-level-{$i}", $collapsed, true)) {
// render level-$i section collapsed
}
Repeat that for each depth you support (1…9). If you discover you need more than 9, just bump MAX_CATEGORY_LEVELS.
# from HumHub root
chmod +x protected/modules/abcdirectory/resources/less/build.sh
cd protected/modules/abcdirectory/resources/less
./build.sh
cd ../../../.. # back to HumHub root
rm -rf protected/runtime/cache/* protected/runtime/assets/*
Result: your admin “Classified spaces browser” settings now list a checkbox for each of Category Level 1–9, letting you turn on/off collapsing of each depth. The front-end should loop over those selections and collapse exactly the levels you choose.
In your run() method you’re currently only passing those three sections (mySpaces, otherSpaces, and the catch-all JS slug map). To drive an arbitrary number of category-level sections, you need to:
Configuration::MAX_CATEGORY_LEVELS)renderLevel($lvl))$params you send to the view, naming them spaceBrowserSectionLevel1, spaceBrowserSectionLevel2, …, spaceBrowserSectionLevel9Here’s exactly how to patch your run():
public function run()
{
// … your existing query setup …
$mySpacesCount = (int)$memberQuery->count() + (int)$followingQuery->count();
$otherSpacesCount = (int)$noneQuery->count();
// collect all the params you already have
$params = [
'modal' => $this->modal,
'allSpaceSlugNames' => $allSpaceSlugNames,
'csBrowserId' => $this->csBrowserId,
'mySpacesCount' => $mySpacesCount,
'otherSpacesCount' => $otherSpacesCount,
'showSpaceBrowserSectionMySpacesHeaderCollapse' => $this->showSpaceBrowserSectionMySpacesHeaderCollapse,
'showSpaceBrowserSectionOtherSpacesHeaderCollapse'=> $this->showSpaceBrowserSectionOtherSpacesHeaderCollapse,
];
// ─── NEW: inject each Category Level section ───────────────────────
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = $config::MAX_CATEGORY_LEVELS;
for ($lvl = 1; $lvl <= $maxLevels; $lvl++) {
// assume you have a method that builds the HTML for level $lvl
$sectionHtml = $this->renderLevel($lvl);
// only pass it if non-empty (optional)
if (trim($sectionHtml) !== '') {
$params["spaceBrowserSectionLevel{$lvl}"] = $sectionHtml;
}
}
// ────────────────────────────────────────────────────────────────
// merge with any other params you may have
$allParams = array_merge($this->getParams(), $params);
return $this->render('spaceBrowser', $allParams);
}
Right after you build $otherSpacesCount, before you call $this->render(), insert the loop above.
You’ll also need a helper method in your widget, e.g.:
protected function renderLevel(int $level): string
{
// fetch the root category for this depth,
// generate your <ul><li>…</li></ul> HTML,
// and return it as a string.
// Return empty string if no spaces exist at that level.
}
In your view (spaceBrowser.php), use those spaceBrowserSectionLevelN variables in your for-loop to wrap each in a Collapse::widget.
With these additions, your widget will supply spaceBrowserSectionLevel1 … spaceBrowserSectionLevel9 (or fewer if empty) to the view, and your view’s for-loop will render each one wrapped in a collapsible panel.
Below is exactly what you need to make “Max category levels” a configurable value in your Settings UI instead of a hard-coded constant. We’ll:
$maxCategoryLevel property to your Configuration model.MAX_CATEGORY_LEVELS.File
protected/modules/abcdirectory/models/Configuration.php
/**
* Maximum number of nested category levels to expose for collapse toggles.
* @var int
*/
public $maxCategoryLevel = 9;
Place it just below your existing $getSpaceImageFromCategory and $showSpaceIdColumn declarations.
rules() to validate itFind the rules() method and add:
[['maxCategoryLevel'], 'integer', 'min' => 1, 'max' => 20],
for example:
public function rules(): array
{
return [
[['removeDefaultSpaceBrowser', 'removeDefaultSpaceDirectory', 'spaceBreadcrumb', 'getSpaceImageFromCategory', 'showSpaceIdColumn'], 'boolean'],
[['maxCategoryLevel'], 'integer', 'min' => 1, 'max' => 20],
[['abcdirectoryBrowserSortOrder'], 'integer', 'max' => 10000],
// …
];
}
In loadBySettings(), after you load showSpaceIdColumn add:
$this->maxCategoryLevel = (int)$this->settingsManager->get('maxCategoryLevel', $this->maxCategoryLevel);
In save(), after setting showSpaceIdColumn add:
$this->settingsManager->set('maxCategoryLevel', $this->maxCategoryLevel);
Anywhere you used Configuration::MAX_CATEGORY_LEVELS, switch to the instance value:
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = $config->maxCategoryLevel;
For example in your widget’s run():
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = $config->maxCategoryLevel;
for ($lvl = 1; $lvl <= $maxLevels; $lvl++) {
// …
}
And in getSpaceBrowserCollapsedSectionOptions(), change the loop to:
public static function getSpaceBrowserCollapsedSectionOptions(): array
{
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = $config->maxCategoryLevel;
$options = [
self::SPACE_BROWSER_COLLAPSED_SECTION_MY_SPACES => Yii::t('AbcdirectoryModule.config', 'Spaces I am a member of'),
];
for ($lvl = 1; $lvl <= $maxLevels; $lvl++) {
$key = 'category-level-' . $lvl;
$label = Yii::t('AbcdirectoryModule.config', 'Category Level {n}', ['n' => $lvl]);
$options[$key] = $label;
}
// … unclassified / other …
return $options;
}
File
protected/modules/abcdirectory/views/config/index.php
Inside the “Classified spaces browser” collapsible, add right before the spaceBrowserCollapsedSections field:
<?= $form->field($model, 'maxCategoryLevel')
->input('number', ['min' => 1, 'max' => 20])
->label(Yii::t('AbcdirectoryModule.config', 'Max category levels'))
->hint(Yii::t('AbcdirectoryModule.config', 'How many nested category levels to show controls for (e.g., 1–9).')) ?>
Your section might now look like:
<?= $form->beginCollapsibleFields(Yii::t('AbcdirectoryModule.config', 'Classified spaces browser')) ?>
<?= $form->field($model, 'spaceBrowserMembershipSeparation')->checkbox() ?>
<?= $form->field($model, 'spaceBrowserCollapsedSections')->checkboxList(Configuration::getSpaceBrowserCollapsedSectionOptions()) ?>
<?= $form->field($model, 'maxCategoryLevel')
->input('number', ['min' => 1, 'max' => 20])
->label(Yii::t('AbcdirectoryModule.config', 'Max category levels'))
->hint(Yii::t('AbcdirectoryModule.config', 'How many nested category levels to show controls for (e.g., 1–9).')) ?>
<?= $form->endCollapsibleFields() ?>
From your HumHub root:
chmod +x protected/modules/abcdirectory/resources/less/build.sh
cd protected/modules/abcdirectory/resources/less
./build.sh
cd ../../../..
rm -rf protected/runtime/cache/* protected/runtime/assets/*
Now Max category levels is fully configurable in Settings. Your dynamic loops and option lists will respect whatever number the admin chooses (up to your allowable max).
Solving that all 9 (maxLevel) show even you change to max 5 levels.
You might have the problem that your getSpaceBrowserCollapsedSectionOptions() is still looping up to the old constant instead of the saved maxCategoryLevel. You need to switch that method to read the instance value from your config, not the hard-coded constant. Here’s the exact patch:
getSpaceBrowserCollapsedSectionOptions()File
protected/modules/abcdirectory/models/Configuration.php
Replace the entire method (which currently probably looks like this)—
public static function getSpaceBrowserCollapsedSectionOptions(): array
{
$options = [
self::SPACE_BROWSER_COLLAPSED_SECTION_MY_SPACES => Yii::t('AbcdirectoryModule.config', 'Spaces I am a member of'),
];
for ($lvl = 1; $lvl <= self::MAX_CATEGORY_LEVELS; $lvl++) {
$key = 'category-level-' . $lvl;
$label = Yii::t('AbcdirectoryModule.config', 'Category Level {n}', ['n' => $lvl]);
$options[$key] = $label;
}
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_UNCLASSIFIED_SPACES] = Yii::t('AbcdirectoryModule.config', 'Unclassified spaces');
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_OTHER_SPACES] = Yii::t('AbcdirectoryModule.config', 'Spaces I\'m not a member of');
return $options;
}
With this version that reads your saved maxCategoryLevel:
public static function getSpaceBrowserCollapsedSectionOptions(): array
{
// grab the instance so we can read the admin‐saved value
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = (int)$config->maxCategoryLevel;
$options = [
self::SPACE_BROWSER_COLLAPSED_SECTION_MY_SPACES => Yii::t('AbcdirectoryModule.config', 'Spaces I am a member of'),
];
for ($lvl = 1; $lvl <= $maxLevels; $lvl++) {
$key = 'category-level-' . $lvl;
$label = Yii::t('AbcdirectoryModule.config', 'Category Level {n}', ['n' => $lvl]);
$options[$key] = $label;
}
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_UNCLASSIFIED_SPACES] = Yii::t('AbcdirectoryModule.config', 'Unclassified spaces');
$options[self::SPACE_BROWSER_COLLAPSED_SECTION_OTHER_SPACES] = Yii::t('AbcdirectoryModule.config', 'Spaces I\'m not a member of');
return $options;
}
You can leave MAX_CATEGORY_LEVELS defined, but it’s no longer used. If you like, delete it at the top of the class:
- public const MAX_CATEGORY_LEVELS = 9;
From your HumHub root:
# recompile CSS (if you touched any LESS; otherwise skip)
chmod +x protected/modules/abcdirectory/resources/less/build.sh
cd protected/modules/abcdirectory/resources/less && ./build.sh && cd ../../../..
# clear Yii caches & assets
rm -rf protected/runtime/cache/* protected/runtime/assets/*
With this change, your checkbox-list will always reflect the live maxCategoryLevel stored in settings.
Sometimes your maxCategoryLevel property is still commented out, so although you’re saving and loading its value, the instance never actually has that property defined—thus your loop still falls back to self::MAX_CATEGORY_LEVELS. Let’s fix that.
File:
protected/modules/abcdirectory/models/Configuration.php
Find this commented line:
/**
* Maximum number of nested category levels to expose for collapse toggles.
* @var int
*/
//public $maxCategoryLevel = 9;
Change to:
/**
* Maximum number of nested category levels to expose for collapse toggles.
* @var int
*/
public $maxCategoryLevel = 9;
Right after your existing attributeLabels() array, append:
'maxCategoryLevel' => Yii::t('AbcdirectoryModule.config', 'Max category levels'),
And in attributeHints(), append:
'maxCategoryLevel' => Yii::t('AbcdirectoryModule.config', 'How many nested category levels to show controls for (e.g., 1–9).'),
Your methods should look roughly like:
public function attributeLabels(): array
{
return [
// … other labels …
'getSpaceImageFromCategory' => Yii::t('AbcdirectoryModule.config', 'Use category image.'),
'maxCategoryLevel' => Yii::t('AbcdirectoryModule.config', 'Max category levels'),
];
}
public function attributeHints(): array
{
return [
// … other hints …
'getSpaceImageFromCategory' => Yii::t('AbcdirectoryModule.config', 'If a space has no header image, use the image of the related category or parent category.'),
'maxCategoryLevel' => Yii::t('AbcdirectoryModule.config', 'How many nested category levels to show controls for (e.g., 1–9).'),
];
}
From your HumHub root:
# Recompile your module CSS if needed:
chmod +x protected/modules/abcdirectory/resources/less/build.sh
cd protected/modules/abcdirectory/resources/less && ./build.sh && cd ../../../..
# Clear Yii caches & assets:
rm -rf protected/runtime/cache/* protected/runtime/assets/*
With the property now declared, your code in getSpaceBrowserCollapsedSectionOptions() will correctly use the admin-saved maxCategoryLevel instead of the old constant.
The “Class …widgets\Configuration not found” error simply means PHP is looking for Configuration in the widgets namespace instead of your models namespace. You have two fixes—either import the correct class at the top, or fully qualify it in your code. Here’s exactly what to change in SpaceBrowser.php.
SpaceBrowser.php, add:<?php
namespace humhub\modules\abcdirectory\widgets;
use Yii;
use humhub\modules\abcdirectory\models\Configuration; // ← add this
use humhub\modules\ui\view\components\Widget;
// … the rest of your imports …
This ensures any un-prefixed Configuration::… refers to the model.
renderLevel(), replace:$categories = Configuration::getInstance()
->getCategoryQueryForLevel($level)
->all();
with something like:
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$categories = $config->getCategoryQueryForLevel($level)->all();
This both avoids a non-existent getInstance() and uses the correct config object.
After these two edits:
SpaceBrowser.php.Clear caches (from your HumHub root):
rm -rf protected/runtime/cache/* protected/runtime/assets/*
/abcdirectory/browse/index) and the widget should no longer crash.Why this works
By importing humhub\modules\abcdirectory\models\Configuration at the top, PHP knows where to find Configuration. Then by fetching your module’s real config via Yii::$app->getModule('abcdirectory')->getConfiguration(), you avoid relying on a missing static getInstance() method and use the same config object you’ve been saving your settings into.
Perhaps you still have this line at the top of your view:
$config = Configuration::getInstance(); // ← THIS MUST GO
Since your Configuration model has no getInstance(), that call will always fatally error. Let’s remove it and fetch the configuration the same way everywhere else:
Open
protected/modules/abcdirectory/widgets/views/spaceBrowser.php
Delete any occurrence of:
$config = Configuration::getInstance();
Make sure the very top of the file looks like this (one <?php, all your use statements, then setup):
<?php
/**
* Classified Spaces browser
*
* @var \humhub\modules\ui\view\components\View $this
* @var bool $modal
* @var \humhub\modules\abcdirectory\models\AbcdirectoryCategory $rootCategory
* @var array<int,string> $allSpaceSlugNames
* @var string $csBrowserId
* @var string|null $spaceBrowserSectionAll
* @var string|null $spaceBrowserSectionMySpaces
* @var string|null $spaceBrowserSectionOtherSpaces
* @var array $spaceBrowserCollapsedSections
* @var int $mySpacesCount
* @var int $otherSpacesCount
*/
use Yii;
use humhub\libs\Html;
use humhub\modules\abcdirectory\assets\AssetsSpaceBrowser;
use humhub\modules\abcdirectory\models\Configuration;
use humhub\modules\abcdirectory\widgets\Collapse;
use humhub\modules\space\models\Space;
use humhub\modules\ui\view\components\View;
AssetsSpaceBrowser::register($this);
$this->registerJsConfig('abcdirectory.spaceBrowser', ['allSpaceSlugNames' => $allSpaceSlugNames]);
// Fetch the saved config instance
$config = Yii::$app->getModule('abcdirectory')->getConfiguration();
$maxLevels = (int)$config->maxCategoryLevel;
$csbAllSpacesId = $csBrowserId . '-accordion-all-spaces';
$csbMySpacesId = $csBrowserId . '-accordion-my-spaces';
$csbOtherSpacesId = $csBrowserId . '-accordion-other-spaces';
Save the file.
Clear HumHub’s caches and assets:
cd /path/to/humhub
rm -rf protected/runtime/cache/* protected/runtime/assets/*
Reload the browser.
With that removed, your view will no longer attempt the undefined getInstance() call and will instead correctly pull in your maxCategoryLevel setting.