chore: sync module from working instance and add install guide

This commit is contained in:
Kelin Rescue Hub
2026-04-09 14:16:53 -04:00
parent 87a59e5a0a
commit 69c348ce27
9 changed files with 360 additions and 59 deletions

55
INSTALL.md Normal file
View File

@@ -0,0 +1,55 @@
# Space Profiles Installation Guide
This guide installs the `space_profiles` module in a reusable way for any HumHub instance.
## 1. Requirements
- HumHub `1.14+`
- Module directory access on the target instance
- Optional but recommended: `rescue_foundation` module
## 2. Clone into HumHub Modules Directory
The folder name must be exactly `space_profiles`.
```bash
git clone https://gitea.kelinreij.duckdns.org/humhub-modules/space-profiles.git \
/var/www/localhost/htdocs/protected/modules/space_profiles
```
If the folder already exists:
```bash
cd /var/www/localhost/htdocs/protected/modules/space_profiles
git pull
```
## 3. Enable the Module
In HumHub UI:
1. Go to `Administration` -> `Modules`.
2. Enable `Space Profiles`.
3. Enable it per space where needed.
## 4. Run Migrations
From the HumHub app host/container:
```bash
php /var/www/localhost/htdocs/protected/yii migrate/up \
--include-module-migrations=1 --interactive=0
```
## 5. Verify
1. Open a space with module enabled.
2. Confirm profile page renders the template sections.
3. Confirm space settings page saves expected profile fields.
## Docker Example
```bash
docker exec humhub php /var/www/localhost/htdocs/protected/yii migrate/up \
--include-module-migrations=1 --interactive=0
```

View File

@@ -7,8 +7,10 @@ use humhub\modules\content\components\ContentContainerControllerAccess;
use humhub\modules\rescue_foundation\widgets\RescueSettingsMenu;
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\models\forms\SpaceProfileForm;
use humhub\modules\space_profiles\services\ModuleSetupService;
use Yii;
use yii\web\UploadedFile;
use yii\web\BadRequestHttpException;
class SettingsController extends ContentContainerController
{
@@ -41,4 +43,34 @@ class SettingsController extends ContentContainerController
'subNav' => $subNav,
]);
}
public function actionSetup()
{
if (!Yii::$app->request->isPost) {
throw new BadRequestHttpException('Invalid request method.');
}
if (!$this->contentContainer instanceof Space) {
$this->view->error(Yii::t('SpaceProfilesModule.base', 'Setup can only be run inside a space.'));
return $this->redirect($this->contentContainer->createUrl('/space_profiles/settings'));
}
try {
$result = ModuleSetupService::runForSpace($this->contentContainer);
$appliedCount = count($result['applied'] ?? []);
if ($appliedCount > 0) {
$this->view->success(Yii::t('SpaceProfilesModule.base', 'Setup completed. Applied {count} migration(s).', [
'count' => $appliedCount,
]));
} else {
$this->view->success(Yii::t('SpaceProfilesModule.base', 'Setup completed. No pending migrations were found.'));
}
} catch (\Throwable $e) {
Yii::error($e, 'space_profiles.setup');
$this->view->error(Yii::t('SpaceProfilesModule.base', 'Setup failed. Please check logs and try again.'));
}
return $this->redirect($this->contentContainer->createUrl('/space_profiles/settings'));
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace humhub\modules\space_profiles\services;
use humhub\modules\space\models\Space;
use Yii;
class ModuleSetupService
{
public static function runForSpace(Space $space): array
{
$result = static::applyModuleMigrations();
$defaultUrl = $space->createUrl('/space_profiles/profile/view');
$settings = $space->getSettings();
$settings->set('indexUrl', $defaultUrl);
$settings->set('indexGuestUrl', $defaultUrl);
$result['defaultHomeUrl'] = $defaultUrl;
return $result;
}
private static function applyModuleMigrations(): array
{
$migrationDir = dirname(__DIR__) . '/migrations';
$files = glob($migrationDir . '/m*.php') ?: [];
sort($files, SORT_NATURAL);
$existingVersions = Yii::$app->db->createCommand('SELECT version FROM migration')->queryColumn();
$history = array_fill_keys($existingVersions, true);
$applied = [];
$skipped = [];
foreach ($files as $file) {
$version = pathinfo($file, PATHINFO_FILENAME);
if (isset($history[$version])) {
$skipped[] = $version;
continue;
}
if (!class_exists($version, false)) {
require_once $file;
}
if (!class_exists($version, false)) {
throw new \RuntimeException('Migration class not found: ' . $version);
}
$migration = new $version();
$ok = method_exists($migration, 'safeUp') ? $migration->safeUp() : $migration->up();
if ($ok === false) {
throw new \RuntimeException('Migration failed: ' . $version);
}
Yii::$app->db->createCommand()->insert('migration', [
'version' => $version,
'apply_time' => time(),
])->execute();
$applied[] = $version;
$history[$version] = true;
}
return [
'applied' => $applied,
'skipped' => $skipped,
];
}
}

View File

@@ -0,0 +1,52 @@
<?php
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\models\SpaceProfile;
use yii\helpers\Html;
/* @var Space $space */
/* @var SpaceProfile|null $profile */
/* @var string $spaceDescription */
?>
<div class="panel panel-default" style="margin-bottom:16px;">
<div class="panel-body">
<?php if (!$profile): ?>
<div class="text-muted">
<?= Yii::t('SpaceProfilesModule.base', 'Contact details will appear after the profile is published.') ?>
</div>
<?php else: ?>
<div style="font-weight:700;font-size:18px;text-align:center;margin-bottom:12px;">
<?= Html::encode($space->name) ?>
</div>
<div><?= Html::encode($profile->address) ?></div>
<div style="margin-bottom:10px;"><?= Html::encode($profile->city) ?>, <?= Html::encode($profile->state) ?> <?= Html::encode($profile->zip) ?></div>
<div style="margin-bottom:6px;">
<strong><?= Yii::t('SpaceProfilesModule.base', 'Email') ?>:</strong>
<a href="mailto:<?= Html::encode($profile->email) ?>"><?= Html::encode($profile->email) ?></a>
</div>
<div>
<strong><?= Yii::t('SpaceProfilesModule.base', 'Phone') ?>:</strong>
<a href="tel:<?= Html::encode(preg_replace('/[^0-9+]/', '', (string)$profile->phone) ?? '') ?>"><?= Html::encode($profile->phone) ?></a>
</div>
<?php if ($spaceDescription !== '' || trim((string)$profile->mission_statement) !== '' || trim((string)$profile->animals_we_accept) !== ''): ?>
<hr style="margin:12px 0;">
<?php endif; ?>
<?php if ($spaceDescription !== ''): ?>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($spaceDescription)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->mission_statement) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Mission Statement') ?>:</strong></div>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($profile->mission_statement)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->animals_we_accept) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Animals We Accept') ?>:</strong></div>
<div><?= nl2br(Html::encode($profile->animals_we_accept)) ?></div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>

View File

@@ -4,7 +4,6 @@ use yii\helpers\Html;
/* @var \humhub\modules\space\models\Space $space */
/* @var \humhub\modules\space_profiles\models\SpaceProfile $profile */
/* @var string $searchBlockWidget */
?>
<div class="media" style="margin-bottom:20px;">
@@ -30,16 +29,6 @@ use yii\helpers\Html;
<section style="margin-bottom:20px;"><?= $profile->body_html ?></section>
<?php endif; ?>
<section style="margin-bottom:20px;">
<?php if (class_exists($searchBlockWidget)): ?>
<?= $searchBlockWidget::widget(['contentContainer' => $space]) ?>
<?php else: ?>
<div class="well well-sm" style="margin-bottom:0;">
<?= Yii::t('SpaceProfilesModule.base', 'Animal Management plugin integration point is ready. The block will appear here when that plugin is installed.') ?>
</div>
<?php endif; ?>
</section>
<?php if (!empty($profile->footer_html)): ?>
<section><?= $profile->footer_html ?></section>
<?php endif; ?>

View File

@@ -1,8 +1,11 @@
<?php
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\Sidebar as SpaceSidebar;
use humhub\modules\space_profiles\components\TemplateRegistry;
use humhub\modules\space_profiles\models\SpaceProfile;
use humhub\modules\space_profiles\widgets\RescueInfoSidebar;
use yii\web\View;
use yii\helpers\Html;
/* @var Space $space */
@@ -16,14 +19,6 @@ if ($profile && !empty($profile->background_image_path)) {
$searchBlockWidget = '\\humhub\\modules\\animal_management\\widgets\\SearchAnimalProfilesBlock';
$templateView = TemplateRegistry::resolveView($profile ? $profile->template_key : null);
$spaceDescription = trim((string)$space->about);
if ($spaceDescription === '') {
$spaceDescription = trim((string)$space->description);
}
if ($spaceDescription === '') {
$spaceDescription = trim((string)($profile?->description ?? ''));
}
?>
<div class="panel panel-default rescue-profile-scope" style="<?= $backgroundStyle ?>">
@@ -42,52 +37,58 @@ if ($spaceDescription === '') {
<?= $this->render($templateView, [
'space' => $space,
'profile' => $profile,
'searchBlockWidget' => $searchBlockWidget,
]) ?>
<?php endif; ?>
</div>
</div>
<?php $this->beginBlock('sidebar'); ?>
<div class="panel panel-default">
<div class="panel-body">
<?php if (!$profile): ?>
<div class="text-muted">
<?= Yii::t('SpaceProfilesModule.base', 'Contact details will appear after the profile is published.') ?>
</div>
<?php else: ?>
<div style="font-weight:700;font-size:18px;text-align:center;margin-bottom:12px;">
<?= Html::encode($space->name) ?>
</div>
<div><?= Html::encode($profile->address) ?></div>
<div style="margin-bottom:10px;"><?= Html::encode($profile->city) ?>, <?= Html::encode($profile->state) ?> <?= Html::encode($profile->zip) ?></div>
<div style="margin-bottom:6px;">
<strong><?= Yii::t('SpaceProfilesModule.base', 'Email') ?>:</strong>
<a href="mailto:<?= Html::encode($profile->email) ?>"><?= Html::encode($profile->email) ?></a>
</div>
<div>
<strong><?= Yii::t('SpaceProfilesModule.base', 'Phone') ?>:</strong>
<a href="tel:<?= Html::encode(preg_replace('/[^0-9+]/', '', (string)$profile->phone) ?? '') ?>"><?= Html::encode($profile->phone) ?></a>
</div>
<?php
$sidebarWidgets = [];
<?php if ($spaceDescription !== '' || trim((string)$profile->mission_statement) !== '' || trim((string)$profile->animals_we_accept) !== ''): ?>
<hr style="margin:12px 0;">
<?php endif; ?>
if ($profile && class_exists($searchBlockWidget)) {
$sidebarWidgets[] = [$searchBlockWidget, ['contentContainer' => $space], ['sortOrder' => 20]];
}
<?php if ($spaceDescription !== ''): ?>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($spaceDescription)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->mission_statement) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Mission Statement') ?>:</strong></div>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($profile->mission_statement)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->animals_we_accept) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Animals We Accept') ?>:</strong></div>
<div><?= nl2br(Html::encode($profile->animals_we_accept)) ?></div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
echo SpaceSidebar::widget([
'space' => $space,
'widgets' => $sidebarWidgets,
]);
?>
<?php $this->endBlock(); ?>
<?php if ($profile): ?>
<div id="space-profile-left-rescue-info" style="display:none;">
<?= RescueInfoSidebar::widget(['space' => $space]) ?>
</div>
<?php
$this->registerJs(<<<JS
(function() {
var panel = document.getElementById('space-profile-left-rescue-info');
if (!panel) {
return;
}
var navContainer = document.querySelector('.layout-nav-container');
if (!navContainer) {
return;
}
var menu = navContainer.querySelector('#space-main-menu');
panel.style.display = 'block';
panel.style.marginTop = '12px';
if (menu && menu.parentNode === navContainer) {
if (menu.nextSibling) {
navContainer.insertBefore(panel, menu.nextSibling);
} else {
navContainer.appendChild(panel);
}
} else {
navContainer.appendChild(panel);
}
})();
JS
, View::POS_END);
?>
<?php endif; ?>

View File

@@ -20,6 +20,25 @@ $profile = $model->getProfile();
<?php endif; ?>
<div class="panel-body">
<div class="well well-sm" style="margin-bottom:14px;">
<div style="font-weight:700;margin-bottom:6px;"><?= Yii::t('SpaceProfilesModule.base', 'Module Setup') ?></div>
<div style="margin-bottom:8px;">
<?= Yii::t('SpaceProfilesModule.base', 'Space Profiles publishes a branded rescue profile page for this space and can set it as the default space home page.') ?>
</div>
<div style="margin-bottom:10px;">
<?= Yii::t('SpaceProfilesModule.base', 'Run setup to apply any pending Space Profiles migrations and configure this space home route automatically.') ?>
</div>
<?= Html::a(
Yii::t('SpaceProfilesModule.base', 'Run Space Profiles Setup'),
$model->contentContainer->createUrl('/space_profiles/settings/setup'),
[
'class' => 'btn btn-primary btn-sm',
'data-method' => 'post',
'data-confirm' => Yii::t('SpaceProfilesModule.base', 'Run Space Profiles setup now for this space?'),
]
) ?>
</div>
<div class="help-block">
<?= Yii::t('SpaceProfilesModule.base', 'Configure your rescue profile content, optional HTML regions, and branding assets.') ?>
</div>

View File

@@ -0,0 +1,31 @@
<?php
namespace humhub\modules\space_profiles\widgets;
use humhub\components\Widget;
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\models\SpaceProfile;
class RescueInfoSidebar extends Widget
{
public Space $space;
public function run()
{
$profile = SpaceProfile::findOne(['contentcontainer_id' => $this->space->contentcontainer_id]);
$spaceDescription = trim((string)$this->space->about);
if ($spaceDescription === '') {
$spaceDescription = trim((string)$this->space->description);
}
if ($spaceDescription === '') {
$spaceDescription = trim((string)($profile?->description ?? ''));
}
return $this->render('rescueInfoSidebar', [
'space' => $this->space,
'profile' => $profile,
'spaceDescription' => $spaceDescription,
]);
}
}

View File

@@ -0,0 +1,52 @@
<?php
use humhub\modules\space\models\Space;
use humhub\modules\space_profiles\models\SpaceProfile;
use yii\helpers\Html;
/* @var Space $space */
/* @var SpaceProfile|null $profile */
/* @var string $spaceDescription */
?>
<div class="panel panel-default" style="margin-bottom:16px;">
<div class="panel-body">
<?php if (!$profile): ?>
<div class="text-muted">
<?= Yii::t('SpaceProfilesModule.base', 'Contact details will appear after the profile is published.') ?>
</div>
<?php else: ?>
<div style="font-weight:700;font-size:18px;text-align:center;margin-bottom:12px;">
<?= Html::encode($space->name) ?>
</div>
<div><?= Html::encode($profile->address) ?></div>
<div style="margin-bottom:10px;"><?= Html::encode($profile->city) ?>, <?= Html::encode($profile->state) ?> <?= Html::encode($profile->zip) ?></div>
<div style="margin-bottom:6px;">
<strong><?= Yii::t('SpaceProfilesModule.base', 'Email') ?>:</strong>
<a href="mailto:<?= Html::encode($profile->email) ?>"><?= Html::encode($profile->email) ?></a>
</div>
<div>
<strong><?= Yii::t('SpaceProfilesModule.base', 'Phone') ?>:</strong>
<a href="tel:<?= Html::encode(preg_replace('/[^0-9+]/', '', (string)$profile->phone) ?? '') ?>"><?= Html::encode($profile->phone) ?></a>
</div>
<?php if ($spaceDescription !== '' || trim((string)$profile->mission_statement) !== '' || trim((string)$profile->animals_we_accept) !== ''): ?>
<hr style="margin:12px 0;">
<?php endif; ?>
<?php if ($spaceDescription !== ''): ?>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($spaceDescription)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->mission_statement) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Mission Statement') ?>:</strong></div>
<div style="margin-bottom:10px;"><?= nl2br(Html::encode($profile->mission_statement)) ?></div>
<?php endif; ?>
<?php if (trim((string)$profile->animals_we_accept) !== ''): ?>
<div style="margin-bottom:4px;"><strong><?= Yii::t('SpaceProfilesModule.base', 'Animals We Accept') ?>:</strong></div>
<div><?= nl2br(Html::encode($profile->animals_we_accept)) ?></div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>