chore: bootstrap module from working instance and add install guide

This commit is contained in:
Kelin Rescue Hub
2026-04-09 14:18:10 -04:00
parent 97ad7da6f4
commit 6cda47760e
35 changed files with 6267 additions and 4 deletions

View File

@@ -0,0 +1,271 @@
<?php
namespace humhub\modules\donations\models\forms;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\donations\models\DonationGoal;
use humhub\modules\donations\models\DonationProviderConfig;
use humhub\modules\rescue_foundation\components\UploadStandards;
use Yii;
use yii\base\Model;
use yii\helpers\FileHelper;
use yii\web\UploadedFile;
class DonationGoalForm extends Model
{
public ContentContainerActiveRecord $contentContainer;
public $id = null;
public $goal_type = DonationGoal::TYPE_RESCUE_GENERAL;
public $target_animal_id = null;
public $title = '';
public $description = '';
public $image_path = '';
public UploadedFile|string|null $imageFile = null;
public $imageGalleryPath = '';
public $target_amount = 0.0;
public $current_amount = 0.0;
public $is_active = true;
public function rules()
{
return [
[['goal_type', 'title', 'target_amount'], 'required'],
[['id', 'target_animal_id'], 'integer'],
[['description'], 'string'],
[['target_amount', 'current_amount'], 'number', 'min' => 0],
[['is_active'], 'boolean'],
[['goal_type'], 'in', 'range' => array_keys(DonationGoal::goalTypeOptions())],
[['title'], 'string', 'max' => 190],
[['image_path'], 'string', 'max' => 255],
[['imageGalleryPath'], 'string', 'max' => 500],
[['imageGalleryPath'], 'validateGalleryImageSelection'],
[['imageFile'], 'file',
'skipOnEmpty' => true,
'extensions' => UploadStandards::imageExtensions(),
'checkExtensionByMimeType' => true,
'mimeTypes' => UploadStandards::imageMimeTypes(),
'maxSize' => UploadStandards::IMAGE_MAX_BYTES,
],
[['target_animal_id'], 'required', 'when' => function (self $model) {
return $model->goal_type === DonationGoal::TYPE_ANIMAL;
}, 'whenClient' => "function () { return $('#donationgoalform-goal_type').val() === 'animal'; }"],
];
}
public function attributeLabels()
{
return [
'target_animal_id' => Yii::t('DonationsModule.base', 'Target Animal'),
'target_amount' => Yii::t('DonationsModule.base', 'Target Amount'),
'current_amount' => Yii::t('DonationsModule.base', 'Current Amount'),
'imageGalleryPath' => Yii::t('DonationsModule.base', 'Image'),
'imageFile' => Yii::t('DonationsModule.base', 'Image Upload'),
];
}
public function loadFromGoal(DonationGoal $goal): void
{
$this->id = (int)$goal->id;
$this->goal_type = (string)$goal->goal_type;
$this->target_animal_id = $goal->target_animal_id !== null ? (int)$goal->target_animal_id : null;
$this->title = (string)$goal->title;
$this->description = (string)$goal->description;
$this->image_path = (string)$goal->image_path;
$this->imageGalleryPath = (string)$goal->image_path;
$this->target_amount = (float)$goal->target_amount;
$this->current_amount = (float)$goal->current_amount;
$this->is_active = (int)$goal->is_active === 1;
}
public function save(): ?DonationGoal
{
if (!$this->validate()) {
return null;
}
$goal = null;
if ($this->id) {
$goal = DonationGoal::findOne([
'id' => $this->id,
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
}
if (!$goal instanceof DonationGoal) {
$goal = new DonationGoal();
$goal->contentcontainer_id = $this->contentContainer->contentcontainer_id;
}
$goal->goal_type = $this->goal_type;
$goal->target_animal_id = $this->goal_type === DonationGoal::TYPE_ANIMAL ? (int)$this->target_animal_id : null;
$goal->title = trim($this->title);
$goal->description = trim($this->description);
$goal->image_path = trim((string)$goal->image_path);
$goal->target_amount = $this->target_amount;
$goal->current_amount = $this->current_amount;
$goal->currency = $this->resolveDefaultCurrency();
$goal->is_active = $this->is_active ? 1 : 0;
if (!$goal->save()) {
$this->addErrors($goal->getErrors());
return null;
}
if ($this->imageFile instanceof UploadedFile) {
$stored = $this->storeImage($this->imageFile, $goal);
if ($stored === null) {
$this->addError('imageFile', Yii::t('DonationsModule.base', 'Could not upload goal image.'));
return null;
}
$goal->image_path = $stored;
} else {
$selectedFromGallery = trim((string)$this->imageGalleryPath);
if ($selectedFromGallery !== '') {
$goal->image_path = $selectedFromGallery;
}
}
if (!$goal->save()) {
$this->addErrors($goal->getErrors());
return null;
}
return $goal;
}
public function validateGalleryImageSelection(string $attribute): void
{
$value = trim((string)$this->$attribute);
if ($value === '') {
return;
}
if ($this->goal_type !== DonationGoal::TYPE_ANIMAL || (int)$this->target_animal_id <= 0) {
if (preg_match('/^(https?:\/\/|\/)/i', $value)) {
return;
}
$this->addError($attribute, Yii::t('DonationsModule.base', 'Please select a valid image source.'));
return;
}
$options = $this->getGalleryImageOptionsForAnimal((int)$this->target_animal_id);
if (!isset($options[$value])) {
$this->addError($attribute, Yii::t('DonationsModule.base', 'Please select an image from this animal gallery or upload a new one.'));
}
}
public function getAnimalOptions(): array
{
$animalClass = 'humhub\\modules\\animal_management\\models\\Animal';
if (!class_exists($animalClass)) {
return [];
}
$tableName = $animalClass::tableName();
if (Yii::$app->db->schema->getTableSchema($tableName, true) === null) {
return [];
}
$animals = $animalClass::find()
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])
->orderBy(['name' => SORT_ASC, 'id' => SORT_ASC])
->all();
$options = [];
foreach ($animals as $animal) {
$label = method_exists($animal, 'getDisplayName')
? (string)$animal->getDisplayName()
: trim((string)($animal->name ?? ''));
if ($label === '') {
$label = 'Animal #' . (int)$animal->id;
}
$options[(int)$animal->id] = $label;
}
return $options;
}
public function getGalleryImageOptionsForAnimal(int $animalId): array
{
$animalGalleryClass = 'humhub\\modules\\animal_management\\models\\AnimalGalleryItem';
if (!class_exists($animalGalleryClass)) {
return [];
}
if (Yii::$app->db->schema->getTableSchema($animalGalleryClass::tableName(), true) === null) {
return [];
}
$items = $animalGalleryClass::find()
->where(['animal_id' => $animalId])
->orderBy(['id' => SORT_DESC])
->all();
$options = [];
foreach ($items as $item) {
$url = trim((string)$item->getImageUrl());
if ($url === '') {
continue;
}
$options[$url] = Yii::t('DonationsModule.base', 'Gallery Image #{id}', [
'id' => (int)$item->id,
]);
}
return $options;
}
private function resolveDefaultCurrency(): string
{
$config = DonationProviderConfig::findOne([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
if ($config instanceof DonationProviderConfig) {
$currency = strtoupper(trim((string)$config->default_currency));
if ($currency !== '') {
return $currency;
}
}
return 'USD';
}
private function storeImage(UploadedFile $file, DonationGoal $goal): ?string
{
$random = Yii::$app->security->generateRandomString(8);
$extension = strtolower((string)$file->extension);
$fileName = 'goal-' . (int)$goal->id . '-' . time() . '-' . $random . '.' . $extension;
$candidateDirs = [
'/uploads/donations/goals/' . (int)$goal->id,
'/uploads/donations-runtime/goals/' . (int)$goal->id,
];
foreach ($candidateDirs as $relativeDir) {
$absoluteDir = Yii::getAlias('@webroot') . $relativeDir;
try {
FileHelper::createDirectory($absoluteDir, 0775, true);
} catch (\Throwable $e) {
Yii::warning($e->getMessage(), 'donations.goal_image_upload_dir');
continue;
}
if (!is_dir($absoluteDir) || !is_writable($absoluteDir)) {
continue;
}
$absolutePath = $absoluteDir . '/' . $fileName;
if ($file->saveAs($absolutePath)) {
return $relativeDir . '/' . $fileName;
}
}
return null;
}
}