mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-11-09 05:24:58 +09:00
* ✨ Show promo redemption history on the promo detail page
Adds a Redemption History table to the product promotion detail page,
listing the individual clients, orders, invoices, phase, status, and
discount amount for every redemption of a promo code. The admin API
(product_promo_redemption_get_list) and backing data model already
existed; the promo list page even linked to a #promo-redemptions
anchor that didn't exist yet. This wires it up.
Refs #588
* 🔒 Don't auto-delete install/ when APP_ENV is ambiguous
checkInstaller() (src/load.php) runs on every bootstrap and, once the
app is installed, deletes the src/install/ directory automatically.
It gated that irreversible delete on Environment::isProduction(),
which silently treats a missing or unrecognized APP_ENV as production.
APP_ENV=dev is only set inside DDEV's web container, so any process
that touches load.php outside of it (a bare `php console.php`, a
stray `php -S`, etc.) resolves to production by default and wipes
install/ with no warning. This happened in a local dev checkout.
Add Environment::isExplicitlyProduction(), which is only true when
APP_ENV is literally "prod", and require it specifically for this
delete. isProduction()'s existing default-to-production behavior is
left untouched elsewhere, since other checks rely on it (email-sending
gates, DNS validation strictness, admin nudges) in ways that are
either non-destructive or where failing toward production is correct.
Also logs before removing the directory instead of doing so silently.
* 🐛 Prevent request query params from overriding the promo_id
CodeRabbit review: merge(request) ran last in the redemption list query,
so a crafted ?promo_id= query param could override the promo actually
being viewed and show another promotion's redemption data. Force
promo_id last so it always reflects the page's own promo, while still
letting per_page/page follow the pagination controls as intended.
(cherry picked from commit 176f295a2b)
56 lines
2.1 KiB
PHP
56 lines
2.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Symfony\Component\Filesystem\Filesystem;
|
|
use Symfony\Component\Filesystem\Path;
|
|
|
|
/**
|
|
* Stubs the global $filesystem used by checkInstaller() so the test never touches real
|
|
* disk. The installer entry point is reported as missing so the exception-throwing branch,
|
|
* which reads the real config via Config::getProperty(), is skipped.
|
|
*/
|
|
function runCheckInstallerWithStubbedFilesystem(bool $expectRemoval): void
|
|
{
|
|
$configPath = PATH_CONFIG;
|
|
$installerEntryPoint = Path::join('install', 'install.php');
|
|
$installDir = Path::normalize('install');
|
|
|
|
$mock = Mockery::mock(Filesystem::class);
|
|
$mock->shouldReceive('exists')->with($configPath)->andReturn(true);
|
|
$mock->shouldReceive('exists')->with($installerEntryPoint)->andReturn(false);
|
|
$mock->shouldReceive('exists')->with($installDir)->andReturn(true);
|
|
|
|
if ($expectRemoval) {
|
|
$mock->shouldReceive('remove')->once()->with('install');
|
|
} else {
|
|
$mock->shouldNotReceive('remove');
|
|
}
|
|
|
|
$previousFilesystem = $GLOBALS['filesystem'] ?? null;
|
|
$GLOBALS['filesystem'] = $mock;
|
|
|
|
try {
|
|
checkInstaller();
|
|
} finally {
|
|
$GLOBALS['filesystem'] = $previousFilesystem;
|
|
}
|
|
}
|
|
|
|
test('checkInstaller does not delete the install directory when APP_ENV is unset', function (): void {
|
|
withAppEnv(null, fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
|
|
});
|
|
|
|
test('checkInstaller does not delete the install directory when APP_ENV holds an unrecognized value', function (): void {
|
|
withAppEnv('staging', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
|
|
});
|
|
|
|
test('checkInstaller does not delete the install directory when APP_ENV is dev or test', function (): void {
|
|
withAppEnv('dev', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
|
|
withAppEnv('test', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: false));
|
|
});
|
|
|
|
test('checkInstaller deletes the install directory when APP_ENV is explicitly prod', function (): void {
|
|
withAppEnv('prod', fn () => runCheckInstallerWithStubbedFilesystem(expectRemoval: true));
|
|
});
|