mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-11-03 10:36:49 +09:00
main
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0eb833e026 |
Reorganise FOSSBilling core library (#4210)
* refactor(library): reorganize FOSSBilling core library
Group Update* prefix into FOSSBilling\Update\{Updater,Patcher,Finalization,ReadinessCheck}
Group Pagination into FOSSBilling\Pagination\{Service,Options,ViewPaginator}
Group System (Config,Environment,Version,Requirements) into FOSSBilling\System
Consolidate Security (Crypt,PasswordManager,Fingerprint,Session,CheckResult/Enum)
Group Exception into FOSSBilling\Exception\{BaseException,InformationException}
Fix I18n case (i18n.php -> I18n/I18n.php) and move Translate -> I18n/Translator
Group Logging (Logger, Monolog -> Factory)
Group Remote (CentralAlerts,ExtensionManager), move ErrorPage -> Http, InjectionAwareInterface -> Interfaces
Update all call sites (di.php, load.php, modules, tests) with no BC shims
Fix import aliasing and phpstan docblocks, remove unused imports
Tests: 2856 passed, phpstan OK
* refactor(validation): migrate Validate into Validation namespace
Split monolithic FOSSBilling\Validate (232 lines, 7 mixed responsibilities)
into focused validators:
- Validation\DomainValidator (isSldValid, isTldValid with DI cache/http)
- Validation\PasswordValidator (isPasswordStrong, passwordsMatch)
- Validation\EmailValidator (isEmailValid)
- Validation\BirthdayValidator (isBirthdayValid)
- Validation\RequiredValidator (checkRequiredParamsForArray)
- Validation\Validator facade retaining same API for $di['validator'] BC-free
Move FOSSBilling\Validate -> FOSSBilling\Validation\Validator,
add FOSSBilling\Validation\DomainValidator via $di['domainValidator'],
keep $di['validator'] as facade. Remove old Validate.php.
Update all call sites and tests (FOSSBilling\Validate -> FOSSBilling\Validation\Validator),
make Validator/DomainValidator non-final for Mockery.
Tests: 2856 passed, phpstan OK
* refactor(tools): split Tools into Utils/Security/Sanitizer/Validation (WIP)
Extract Tools god class (534 lines) into focused helpers:
- Utils\Str (slug, toCamelCase, fromCamelCase)
- Utils\Arr (safeCount, sortByOneKey, getPairsForTableByIds via arr service)
- Utils\Normalizer (normalizeBoolean, normalizePort)
- Utils\Network (isValidHttpInterface, getDefaultInterface, listHttpInterfaces, getExternalIP via network service)
- Utils\Format (humanReadableBytes)
- Security\Credential (generatePassword, session restore tokens)
- Sanitizer\Sanitizer (sanitizeContent, sanitizePlainText, sanitizeMarkdownContent)
- Validation\PhoneValidator + EmailValidator::validateAndSanitizeEmail
Update di.php: replace tools service with arr/network services,
migrate 23 src files to new helpers (FOSSBilling\Utils etc.),
keep Tools.php for remaining tests (to be removed next).
Tests: partial, 11 errors remain for slug mocks (to be fixed).
* refactor(api): introduce Role enum, tighten Api layer (deslopped)
- Add FOSSBilling\Api\Role enum (guest/client/admin) replacing stringly-typed role handling
- Api\Identity now stores Role, exposes getRole():Role + getType():string (BC), typeFromObject():Role
- Api\AbstractApi: make abstract, typed properties (string $ip, ?Module $module, Client|Admin|Guest|null $identity), typed setters/getters, fix BaseException usage
- Api\Proxy: compose Identity VO instead of duplicating type logic, keep lazy dispatcher from container, remove redundant helpers
- Api\Dispatcher: use Role enum and Identity VO, normalize identity, keep direct $di['update_finalization'] lookup, deslop finalization injection and apiClassSuffix helper
- Update tests/Pest IdentityTest to expect Role enum
- Minor: fix Cron/Staff Api call sites for typed identity
Deslop: removed hasModule/hasIdentity/hasService helpers, duplicate forwarding comment, unnecessary setFinalization/constructor injection and apiClassSuffix helper.
* refactor(cache): introduce Driver enum, use typed driver handling
- Add FOSSBilling\Cache\Driver enum (filesystem/redis/memcached)
- CacheFactory: parse config via Driver::tryFrom, match on enum, use ->value for messages, remove unused Tools import
- System\Api\Admin: validate driver via Driver::tryFrom instead of in_array(SUPPORTED_DRIVERS)
* refactor(doctrine+api): address review findings
- Doctrine: remove unused Tools import in DriverManagerFactory
- Doctrine: add Driver/Charset enums (pdo_mysql, utf8/utf8mb4/latin1) and use tryFrom for validation
- Api: make AbstractApi::setModule(object) lenient for test doubles (anonymous class in DispatcherTest)
- Tests: apiEndpoint() now ensures identity via getIdentity() catch and sets Admin/Client/Guest based on Api class
* Type AbstractApi module as Module
* Update Doctrine factory copyright years
* refactor(enums): relocate Client/Order status enums to domain modules
Move FOSSBilling\Enums\ClientStatusEnum -> Box\Mod\Client\Enum\Status
and FOSSBilling\Enums\ClientOrderStatusEnum -> Box\Mod\Order\Enum\Status,
colocating statuses with their owning entities (Client.php:29,
Order.php:24) and removing the generic FOSSBilling\Enums catch-all.
Drops Enum suffix, fixes ClientOrder vs Order naming mismatch, updates
sole consumer Massmailer\Service and deletes src/library/FOSSBilling/Enums/.
* refactor(exception): clean up BaseException and InformationException
- BaseException: type stackTrace(int $length,bool $protected), fix
camelCase ($Length->$length, $my_entry->$arg, $entry_file/file),
debug_backtrace typing, copyright 2025->2026
- InformationException: remove redundant passthrough __construct, now
minimal marker extends BaseException (retains 100+ user-facing
throw sites and Sentry ignore distinction)
* refactor(http): harden Http factories, fix escaping and copyrights
- CsvResponseFactory: validate table name preg_match /^[a-zA-Z0-9_]+$/ and throw
InformationException, add import, copyright 2025->2026
- ErrorPage: escape message via htmlspecialchars in renderPage, use
escapedMessage for fallback and original toggle, copyright 2025->2026
- ExceptionResponseFactory: split rawMessage, htmlspecialchars only for
API_MODE Json, pass raw to ErrorPage (single escape source)
- RequestFactory: copyright 2025->2026
* Clean up locale detection error handling
* refactor(interfaces): split generic Interfaces into domain colocated contracts
- Delete FOSSBilling\Interfaces catch-all (5 files)
- FOSSBilling\Interfaces\ApiArrayInterface -> FOSSBilling\Api\ArrayInterface
(ArrayInterface, removes Api stutter, 60+ entities)
- FOSSBilling\Interfaces\InjectionAwareInterface -> FOSSBilling\Container\InjectionAwareInterface
(Container, 100+ services)
- FOSSBilling\Interfaces\SecurityCheckInterface -> FOSSBilling\Security\CheckInterface
(Security, removes Security stutter, 4 checks)
- FOSSBilling\Interfaces\TimestampInterface -> FOSSBilling\Doctrine\TimestampInterface
(Doctrine, with TimestampTrait)
- FOSSBilling\Interfaces\WidgetProviderInterface -> Box\Mod\Widgets\ProviderInterface
(Widgets module, provider contract for Branding etc)
Update 172 usages, keep InjectionAware/Timestamp short names, shorten
ApiArray/SecurityCheck/WidgetProvider stutter.
* Type logger cache as array in factory
* Update pagination copyright headers
* Update CentralAlerts copyright year
* Clean up sanitizer class imports
* Refine security helpers and enum namespace
* Update system class copyright headers
* Move Twig AppArea enum to root namespace
* Update copyright year in update classes
* Drop legacy Arr/Str wrappers and DI binding
* refactor(utils): complete Tools split and delete god class
- Delete FOSSBilling\Tools (534 lines) god class, remove load.php require/use
- Migrate 16 call sites: di['tools']->validateAndSanitizeEmail -> EmailValidator::validateAndSanitizeEmail (8 Api), News slug -> Str::slug, generatePassword already via Credential, humanReadableBytes via Utils\Format, etc.
- Remove 20 unused use FOSSBilling\Tools imports (Order/Extension/Currency etc.)
- Harden Arr::getPairsForTableByIds with preg_match table validation and backticks (CsvResponseFactory style)
- Remove Str legacy wrappers (slugLegacy/slugInstance/to_camel_case)
- Update 11 test files to remove Tools mocks (Extension/Product/Profile/Custompages/Staff/Client/Email/Invoice/Support) and Container helper
- phpstan 443 OK (one less file), tests 60/6 pre-existing failures (AbstractApi identity/pagination) remain, Tools InvalidCount 54->0 for new code
* Update RequiredParams copyright year
* Update copyright headers to 2026
* Remove dead validation code
* fix(ci): address Rector, CS-Fixer and test failures after library reorg
- Run Rector fixes: RemoveUselessReturnTag, RemoveReadonlyOnReadonlyClass, ReadOnlyProperty, RecastingRemoval
- Run cs:fix (132 files) for import ordering and formatting
- Fix Pest helper to handle Mockery mocks for Admin/Client identities
- Fix DispatcherTest to use Module mocks for extension
- Update Client tests to remove Tools mocks and use new EmailValidator/Url
- Fix Custompages ServiceTest to use real Str::slug and long titles
- Fix Pagination mocks to use Service class
- Fix i18n, InformationException, Requirements, Config namespaces
- Add url to test container with SYSTEM_URL base
- Fix a few remaining service tests (Profile, Extension, Product, Support)
phpstan 451/451 OK, pest now 11 failures (down from 107)
* fix(tests): make suite green after reorg
- Fix Extension ServiceTest InformationException namespace and Str mock
- Fix Product Api Admin paginated products mock
- Fix Client/Service i18n, Profile i18n, and url container
- Fix Custompages long-title slug handling
- All 2998 tests pass (2 skipped), phpstan 451/451
* style: fix cs
* fix(docker): update Version.php path after move to System namespace
* fix(tests): remove remaining Tools references
- Delete obsolete ToolsTest (Tools class removed)
- Remove di['tools'] mocks from Profile ServiceTest
* feat(update): add patch115 to clean up library reorg orphans
Removes stale PSR-4 paths left on disk after refactor/library-reorganization
(Tools split, moved namespaces, low-case i18n) which persist on updated
installs because the updater extracts on top. Uses executeFileActions +
removeEmptyDirectories with exists guards, idempotent.
Bumps fresh-install last_patch to 115 to match latestPatchLevel().
* fix(cache): align TLS + instance_id merge with reorg namespaces
- Use Normalizer::normalizeBoolean instead of removed Tools
- Use BaseException + driver->value for instance_id/TLS errors
- Update tests to expect BaseException
* fix(tests): use US spelling to satisfy spellcheck
Normalise -> Normalize, normalised -> normalized
* refactor(box): move Box legacy globals to semantic namespaces
- Box_App/AppAdmin/AppClient -> FOSSBilling\Http\App/AppAdmin/AppClient
- Box_Authorization -> FOSSBilling\Security\Authorization
- Box_Event/EventDispatcher/EventManager -> FOSSBilling\Event\Event/Dispatcher/Manager
- Codemod 499 references across src/modules, src/di.php, src/index.php, tests
- Fix namespace imports (ArrayObject, Pimple\Container, ReflectionMethod, \RuntimeException, \InvalidArgumentException, \Box\Mod\*, \Override)
- Add Patcher patch116 to clean up orphaned src/library/Box/* + bump last_patch 115->116
* refactor(update): extract Patcher patches to Patch/* orchestrator
- Create FOSSBilling\Update\Patch\PatchInterface + PatchRegistry (explicit MAP, not Finder)
- Move 89 private patch25..patch116 methods to dedicated classes Patch25..Patch116
- Each Patch::apply(Patcher $patcher) delegates via public helpers ($patcher->executeSql etc.)
- Patcher now shrinks from 3624 to 1049 LOC: getPatches() uses PatchRegistry::MAP, applyCorePatches() loops [new PatchClass(), 'apply'] with call_user_func($patch, $this)
- Make helpers public (executeSql, fetchAll, tableHasColumn etc.) and properties public for patch access
- Update UpdatePatcherTest to invoke Patch*::apply and expect 'apply' method, keep ordering/skip checks
- No behavior change: MySQL guard, SchemaSynchronizer, last_patch 116 intact
* fix(spellcheck): add checkperm to dictionary after Box->Http move
* style: apply Rector StringClassNameToClassConstant + typed const
* style: apply Rector IfToNullCoalescing for changed files
* style: apply remaining Rector IfToNullCoalescing
* refactor(update): drop redundant Patch101
ADD INDEX client_order_unpaid_invoice_id_idx is already declared in
Order.php:19 and covered by SchemaSynchronizer's additive sync (safeAddedIndexes).
On PG/SQLite Patch101 never ran (isMysqlDriver guard), MySQL now gets it
via syncPortableSchema after the patch loop. Keeps last_patch at 116,
adds gap 101 alongside 92/95/98.
* refactor(update): make Patch116 superset of 115+116, keep 115 as no-op gap
* fix(spellcheck): add superset/orphans after Patch115/116 merge
* refactor(update): remove Patch115 file, keep 116 as superset
Patch115 orphan cleanup is now handled by Patch116 superset (39 paths).
115 remains a gap (like 92/95/98/101) — installs at 115 still get cleanup via
116's idempotent deletes.
* refactor(update): make Patch39 superset of 38+39, drop Patch38 file
38 (Ispconfig3/Virtualmin) + 39 (srp) are idempotent file deletions.
39 now deletes all 3 orphans; 38 remains gap like 101/115.
Installs at 37 -> 39 and 38 -> 39 both get cleanup via 39 superset.
* fix(spellcheck): add Ispconfig3 for Patch39 superset
* refactor(update): make Patch33 superset of 30-33, drop Patch30-32 files
30 (guzzlehttp) + 31 (htaccess/config.old) + 32 (phpmailer/Box/*) + 33 (FileCache)
are idempotent file deletions. 33 now deletes all 19 orphans; 30-32 remain gaps
like 38/101/115. Installs at 29 -> 33 and 32 -> 33 both get cleanup via 33 superset.
* fix(spellcheck): add idempotently/gaps/FileCache for Patch33 superset
* refactor(update): move gateway/branding helpers into Patch64
- migrateDefaultBrandingAssetsToPublicDirectory and
migrateGatewayAssetsToPublicDirectory were only used by Patch64.
Move them as private helpers inside Patch64 (param Patcher $patcher)
and remove public versions from Patcher.
- Patcher shrinks by ~66 LOC, stays public only for shared helpers.
* Drop getVersion from update patches
* refactor(update): move downloadable helpers into Patch65
- generateDownloadableStoredFilename, copyLegacyDownloadableFile,
migrateDownloadableProduct/Service/OrderStorageKeys were only used
by Patch65 (and internally). Move them as private helpers inside
Patch65 (private array $downloadableStorageMigrationMap + 5 methods
taking Patcher $patcher) and remove public versions + property from
Patcher. Keeps Patcher public API for shared helpers only.
* refactor(update): move email template helpers into Patch52
- getDefaultEmailTemplateData and getDefaultEmailTemplatePath were only
used by Patch52 directly (and via Patcher::restoreLegacy... for 57/90).
Move them as private helpers inside Patch52 (taking Patcher $patcher)
so Patch52 is self-contained. Keep public versions in Patcher for
restoreLegacyDefaultEmailTemplates used by Patch57/90 (duplicate for now,
could also be moved if desired).
* refactor(update): move email template helpers into Patch52/57/90
- getDefaultEmailTemplateData and getDefaultEmailTemplatePath were only
used by Patch52 direct and via Patcher::restoreLegacyDefaultEmailTemplates
for Patch57/90. Move them as private helpers into Patch52 (self-contained)
and duplicate into Patch57/90 alongside private
restoreLegacyDefaultEmailTemplates (which also moved from Patcher).
- Remove public versions from Patcher — keeps Patcher public API for shared
helpers only (executeSql, tableHasColumn etc.). Fixes unrelated
Patcher.php diff (\s vs \s) by removing the methods entirely.
* refactor(update): move custom page slug helpers into Patch102
- allocateUniqueCustomPageSlug and fitCustomPageSlug were only used
by Patch102 (via $patcher->). Move them as private helpers inside
Patch102 (taking Patcher $patcher, using $patcher->fetchOne and
$this->fitCustomPageSlug) and remove public versions from Patcher.
Keeps Patcher public API for shared helpers only.
* refactor(update): make Patcher helpers and patch bookkeeping driver-agnostic
- applyConfigPatches: normalize driver aliases (mysql/pgsql/sqlite -> pdo_*) and use per-driver port defaults (5432 for pgsql, none for sqlite) instead of hardcoded 3306; aligns with DriverManagerFactory::DEFAULT_PORTS.
- setPatchLevel: replace MySQL-only ON DUPLICATE KEY UPDATE with portable SELECT then UPDATE/INSERT so last_patch works on pgsql/sqlite.
- Rename isMysqlDriver -> isLegacyPatchDriver (keep isMysqlDriver as deprecated alias); availablePatches/applyCorePatches now use isLegacyPatchDriver; update SchemeSynchronizer and test docblocks.
- Make tableExists/getTableColumns/getColumnLength/getColumnType/tableHasIndex portable via AbstractSchemaManager + driver-specific fallbacks (information_schema.tables WHERE table_schema='public' for pgsql, sqlite_master for sqlite); MySQL path keeps SHOW COLUMNS/INDEX for BC.
- updateTable: use driver-aware quoting (backticks for mysql, double quotes for pgsql/sqlite) instead of hardcoded backticks.
Verified: phpstan 536/536, cs:fix clean, pest 2990 passed.
* chore: add pg catalog words to spellcheck dictionary
* Remove deprecated compatibility shims
* refactor(doctrine): expand Driver enum and use it throughout
- Driver.php: add PdoPgsql/PdoSqlite cases, tryFromAlias(), defaultPort(), isMysql/isSqlite helpers; was single-case.
- DriverManagerFactory.php: use Driver::tryFromAlias() in normalizeDriver(), Driver::tryFrom()->defaultPort()/isMysql/isSqlite in getDatabaseConfig/buildConnectionParams/applySessionSettings instead of string compares.
- Patcher.php: use Driver enum in applyConfigPatches (alias + defaultPort), isLegacyPatchDriver (isMysql), tableExists/getTableColumns/getColumnLength/getColumnType/tableHasIndex/updateTable (portable via AbstractSchemaManager + enum driver checks).
Ensures single source of truth for supported drivers; phpstan 536/536, pest 2990.
* Unify HTML sanitization across Twig and APIs
Replace the old sanitizer helpers with a shared HtmlSanitizerFactory and reuse context-specific sanitizers for content, markdown, adapter output, and theme settings. This also exposes sanitize_html and sanitize_markdown Twig filters, sanitizes markdown render output and email/signature HTML consistently, and simplifies support/email input cleanup by trimming/null-byte stripping instead of HTML-encoding raw input.
* fix: align PayGateway entities with Doctrine TimestampInterface after merge
* Type HTML sanitizer scheme constants
* style: fix import order and FQCN usage
* refactor(sanitizer): centralize HtmlSanitizerFactory sanitization and remove redundant config
* fix: add commonmark to cspell dictionary
* fix: apply Rector autofixes for library reorganization
Addresses Code Quality failures after merging main:
- remove unnecessary (string)/(bool) recasts (RecastingRemovalRector)
- add strict string casts for null-able args (NullToStrictStringFuncCallArgRector)
Files are limited to the PR's changed PHP set (BASE_SHA...HEAD) so
pre-existing drift elsewhere doesn't block this branch.
* fix: stabilize DomainValidator arrow function type for Rector
Add explicit string type to array_filter callback to prevent
oscillation between NullToStrictString (add cast) and
RecastingRemoval (remove cast) on the tld parameter.
* fix: apply Rector autofixes after merge from main
BinaryOpNullableToInstanceof / WhileNullableToInstanceof
* style: fix CS imports after Rector (Invoice / ServiceLicense FQCN -> imported)
* fix: use FOSSBilling\Event\Event in Antispam isSpam test (Box_Event -> Event reorg)
* Move src/library/FOSSBilling to src/core
First-party namespaced classes now live in src/core, leaving src/library
as the legacy PSR-0 adapter layer (Payment_, Registrar_, Server_).
- composer.json: PSR-4 FOSSBilling\ -> src/core/ (classmap unchanged)
- load.php/install.php: new PATH_CORE constant; pre-autoload requires repointed
- Update/ReadinessCheck: core/ added to the writable-folder checks
- Patch117: removes the obsolete library/FOSSBilling tree on upgraded installs
- Dockerfile: copies src/core in the vendor stages; release rewrites
(Version.php, SentryHelper.php, archive composer remap) repointed
- labeler/CODEOWNERS/cspell/AGENTS.md/test path references updated
- install/sql/content.sql: last_patch seed 116 -> 117
* Rename the FOSSBilling\ namespace to FOSSBilling\Core\
All first-party namespaced classes now live under FOSSBilling\Core\,
matching their src/core location. Clean cut, consistent with the Box\
removal on this branch: released extensions referencing the old names
must update their imports once in the same release.
- namespace/use/FQCN/docblock rewrite across src/ and tests/ (626 files)
- composer.json PSR-4: FOSSBilling\Core\ -> src/core/
- Dockerfile release composer remap updated to match
- SentryHelper: core.class tag for core paths, library.class retained
for the PSR-0 adapter layer
- Doctrine proxy namespaces (prod + test) follow the new prefix
- tests/Unit/FOSSBilling -> tests/Unit/FOSSBilling/Core; template
harness dirname depths adjusted
- ignore Strict/ test-harness findings artifacts
* Refresh the Composer autoloader after restoring the cached vendor tree
The php-build vendor cache is keyed on composer.lock, whose content-hash
does not change for autoload-only composer.json edits. On a cache hit the
install step is skipped entirely, leaving vendor/autoload.php generated
from the old PSR-4 mapping - so the library-to-core reorganization made
Rector fatal with "Trait FOSSBilling\Core\Doctrine\TimestampTrait not
found" while compiling analyzed files. Re-dump the autoloader from the
current composer.json after the cache restore.
* Trim comments on the two hand-written pieces to house style
Patch116 uses two dense lines with a short SHA rather than a commit
title; mirror that for Patch117. Shorten the autoloader-refresh comment
in php-quality-setup to the essentials.
* Fix two stale references from the library-to-core move
- GeoIP\Reader still built its bundled MMDB paths from PATH_LIBRARY
. FOSSBilling - point them at PATH_CORE so the default reader works
now that Patch117 removes the old tree; add existence tests so a
future move can't silently break this again.
- Dispatcher passed Request::getClientIp() (nullable on CLI, e.g. cron)
to the string-typed setIp(), TypeErroring before the first cron task;
coalesce to '' to match AbstractApi's own empty-string default.
* Dockerfile: namespace-wrapped cache sanity check belongs under Core
* Add dasharray to cspell words (SVG stroke-dasharray in branding assets)
Fixes Spellcheck failure on the push CI run.
* Deslop merge resolutions: trim comments, drop dead test stubs, import Patch classes
* Fix stale old-namespace references found in completeness audit
- CONTRIBUTING.md documented src/library/FOSSBilling paths/namespaces
- EntityMigrationCoverageTest read the deleted
library/FOSSBilling/UpdatePatcher.php; scan Patcher.php + Patch
classes instead (proven to detect ported migrations)
* Satisfy Rector 2.8: drop redundant cast, add nullable return type
* Address code review: restore Patch30-32, trim Patch33, drop retired hook call in Patch55
- Patch55 called mod_service('hook')->batchConnect(), removed on main
when the hook runtime was retired; the throw skipped the remaining
spamchecker cleanup. Match main (no hook lines).
- Patch33 had absorbed patches 30-32 cleanups, newly deleting the live
vendor/guzzlehttp (now required via sentry/sentry) for installs past
patch 30. Restore Patch30/31/32 as separate classes matching main and
trim Patch33 to the FileCache.php removal.
* Fix uninitialized Arr helper for group-restricted promos
enrichPromoApiArray built Arr without a container, so any promotion
with client-group restrictions fatally dereferenced null di in
getPairsForTableByIds. Initialize the helper with the service
container (with the file's usual missing-container guard) and add a
regression test.
* Keep client-group lookup portable: drop MySQL backticks
The Tools split introduced backtick quoting around the validated
table name; PostgreSQL rejects it. Main used the validated unquoted
name - restore that and assert the exact SQL in the regression test.
|
||
|
|
7d13d8bd39 |
Fix the toApiArray auditor crashing on try/catch blocks (#4599)
* Fix the toApiArray auditor crashing on try/catch blocks The TryCatch branch read $node->try, which php-parser does not have (the try body is $node->stmts), so the first toApiArray() containing a try/catch passed null to collectAssignments() and the audit test died with a TypeError. The catch and finally nodes were passed in whole, and collectAssignments() has no branch for them, so their bodies were never examined either. Walk the try, catch and finally bodies instead. A new test audits a temporary module whose toApiArray() sets a field conditionally in each of the three; it fails with the TypeError before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the audit test's temporary directory in plain words cspell rejects "toapiarray". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Adam Daley <admdly@users.noreply.github.com> |
||
|
|
bae0bffcfd | Bundle default admin/client themes into a theme package (#4271) | ||
|
|
b364588f30 | Harden release cleanup and updater integrity (#4166) | ||
|
|
6a10ee3e9f |
Migrate admin identity from Model_Admin to Doctrine entity (#4136)
* Migrate admin identity from Model_Admin to Doctrine entity * Fix admin ID access in logger when admin identity is a Doctrine entity * Harden staff service flows and cron admin creation against race conditions |
||
|
|
195921a32d |
Show discounted payment amount on client order details page (#4114)
* Show discounted payment amount on client order details page Mirrors the existing admin-side order details table: when an order has a discount, show 'Order Discount' and 'Payment Amount After Discount' rows alongside the full 'Payment Amount'. Fixes #1723 * Classify PermissiveStub arithmetic TypeErrors as test-infra The new discount rows in mod_order_manage.html.twig (client) do `order.total - order.discount` directly, same as the existing admin template. Under the strict-variables render-everything harness, both resolve to PermissiveStub when unspecified, and PHP has no operator overloading for arithmetic on plain objects, so this throws 'Unsupported operand types'. This is the same class of stub-coercion artifact as the existing 'must be of type float/int' and 'Return value must be of type' entries above it, not a real template bug. * Require PermissiveStub in the operand-type message before downgrading Per CodeRabbit review: the previous check matched any 'Unsupported operand types' TypeError, which would also swallow a genuine arithmetic bug elsewhere in a template as test-infra. PHP's TypeError message includes the operand class names, so requiring PermissiveStub::class in the message keeps the downgrade scoped to the actual stub-coercion case. |
||
|
|
3acbfc3aa1 |
Apply PHP Code Styling & Quality Changes (#4071)
Co-authored-by: FOSSBilling Bot <fossbilling-bot> Co-authored-by: Adam Daley <admdly@users.noreply.github.com> |
||
|
|
63090718da |
Add currency display format overrides (#4053)
* Add currency display format overrides * Fix currency formatting CI tests * Guard currency preview controls |
||
|
|
85fa3dc1b2 |
Refactor HTTP helpers and inject Filesystem via DI (#3900)
* Remove redundant HTTP helper wrappers * Inject Filesystem via DI across services * Fix constructor and Theme DI wiring |
||
|
|
b73e4ce09c |
Replace legacy icons and standardize SVG icon markup (#3758)
* Replace legacy icons and add icon sprite generator * Update package-lock.json * Standardize SVG icon markup and fallback * Render client email as plain text * Fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Add dedicated email signature for templates * Fix Huraga SVG sprite theme resolution (#3760) 🐛 Fix SVG sprite theme resolution --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> |
||
|
|
3c6ab0bd4f |
Apply PHP Code Styling & Quality Changes (#3739)
Co-authored-by: FOSSBilling Bot <fossbilling-bot> |
||
|
|
5ecfeb335e |
Harden templates and API responses against missing data (#3730)
* Guard Activity index template against missing client/staff nested data
The activity log API at Activity/Api/Admin.php:38-49 only injects a
nested 'client' or 'staff' key when the corresponding *_id is set
(via isset() which returns false for null). System events and
client-only events therefore have no 'client' key, while staff-only
events have no 'client' key.
Under strict_variables, '{% if event.client %}' (line 191) throws
"Key 'client' ... does not exist" for any event without a client.
Switch the conditional to event.client_id / event.staff_id (always
present as SQL columns) and add '|default("")' to the nested
event.client.email / event.client.name / event.staff.email / event.staff.name
accesses so deleted-but-referenced records render gracefully.
* Guard Security iplookup template against empty country/asn data
When the GeoIP or ASN lookup fails (IncompleteRecord from MaxMind), the
Service catches and substitutes [] for the missing data. Under
strict_variables, the empty mapping at record.asn / record.country
causes line 48 to throw "sequence/mapping is empty".
Twig's |default filter on a GetAttrExpression compiles to a safe
is-defined check, so it correctly falls through to the placeholder
when the underlying array is empty or the key is missing.
Verified:
- /admin/security/iplookup?ip=192.168.1.1 (private IP, empty asn)
renders the row with 'Unknown' ASN cells.
- /admin/security/iplookup?ip=8.8.8.8 still renders
'15169' / 'GOOGLE' / 'United States'.
* Initialize notes/rel arrays in Support toApiArray so templates render without errors
Two related issues surfaced under strict_variables on the admin support
ticket page:
1. toApiArray only set $data['notes'] inside the foreach loop over
ticket notes. Tickets without any notes therefore had no 'notes' key
and 'ticket.notes|length' in mod_support_ticket.html.twig:42 threw
'Key "notes" does not exist'.
2. _getRelDetails returned [] (empty array) when a ticket has no
related entity. mod_support_ticket.html.twig sets {% set task =
ticket.rel %} and then accesses task.task, task.type, task.status
etc. on the next lines. Any access into an empty mapping throws
under strict_variables.
Fixes:
- Service::toApiArray: always initialize $data['notes'] = [] for admin
identity so the key is present even when empty.
- Service::_getRelDetails: always return the base structure with all
fields (id, type, task, new_value, status) set to null when there
is no related entity. Skip the DB lookups for the non-rel case.
Template hardening (defense-in-depth, in case other code paths return
a different shape):
- mod_support_ticket.html.twig:42: 'ticket.notes|length' -> 'is defined and
ticket.notes|length'.
- mod_support_ticket.html.twig:222: 'ticket.notes' -> 'ticket.notes|default([])'.
- mod_support_tickets.html.twig:238: same is defined guard.
Verified on dev:
- /admin/support/ticket/1 renders without errors.
- API now returns rel as {id: null, type: null, ...} and notes: [] for
tickets without related data.
- 1777 local tests pass, PHPStan clean.
* Harden tests and add Twig/ToApi linters
* Remove redundant setAccessible calls in tests
* Update cspell.json word list
* Add UrlAwarePermissiveContainer & trim errors
* Add geoip stub and fix ParserFactory usage
* Enforce and apply parentheses for new expressions
|
||
|
|
e8db6000ad | Simplify tests and suppress notices in renderer (#3729) | ||
|
|
7baa28e22f |
Enhance Twig strict variables handling and improve test coverage (#3721)
* Import template macros and fix invoices tab id
* Handle nulls in Twig extensions and add tests
* Enable Twig strict_variables and add tests
* Improve Twig strict-variables test harness
Fix several undefined-variable issues in admin templates and harden the strict-variables test harness.
- Templates: guard against missing variables by using default() for order_actions and checking header/type in partial_extensions.twig.
- Loader: CombinedTwigLoader now registers theme and module template paths with namespaces (module_area), and includes email template directories.
- Variable collection: VariableCollectorVisitor now collects keys passed via {% embed %} and {% include %} with { ... } so embedded templates get proper stubs.
- Renderer: StrictTemplateRenderer gains better relative template naming for module templates, additional logging for kb_article parsing, and broader classification of render errors as test-infra (typed-return mismatches, NumberFormatter issues, callable/iterable errors, loader quirks) to reduce false positives.
- Tests: add baseline marker files for strict findings, adjust StrictVariablesTest to treat infra findings as informational while failing on real-bug findings, and write findings JSON for review.
Overall these changes reduce noise from the render-everything harness and make the strict-variables suite more robust and actionable.
* Bump test memory limit to 2G via ini_set in bootstrap
The strict-variables render-everything harness parses every FOSSBilling
template and allocates many stubs and AST nodes, which exceeds the
default 128M limit and causes the pest process to exit with 255
mid-suite. Setting the limit in tests/Pest.php means it applies to both
local 'composer test' and CI 'pest --ci' invocations without needing
to modify the workflow file.
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Scope error_reporting suppression to the render-everything harness
* Replace unsupported 'now' Twig global with 'now'|date filter
The reserved 'now' Twig global is only defined in some contexts and is
not present in the install or admin email-template environments. Under
strict_variables it throws a fatal 'Variable "now" does not exist'
error, which crashes the installer and the email template playground.
Both call sites use the date filter to format the current year, so the
correct fix is to feed the filter a string literal: 'now'|date('Y').
This is the same pattern we used for the admin_default and huraga theme
layouts.
Discovered by hitting the install page on the dev instance after the
strict_variables rollout.
* Make sitemap tolerant of missing product.updated_at
The product guest API (Box\Mod\Product\Service::toApiArray) returns
updated_at only for admin requests, not for guests. The sitemap uses
the guest API, so product.updated_at was always undefined for it.
Before strict_variables this was harmless: the date filter silently
returned null and the template rendered. With strict_variables enabled,
the missing key throws 'Key updated_at does not exist', which
get_custom_page catches and converts to a 404.
Pipe through |default('now') so we fall back to the current date when
the field is missing, while still using the real value when present.
Discovered by the live test 'urls return ok status' failing on sitemap.xml
in CI.
* Remove stray blank line in StrictTemplateRenderer
* Fix four templates for strict_variables Cypress failures
The Cypress tests run against a fresh install with strict_variables
enabled, which catches undefined key access in templates that the
non-strict mode silently allowed.
1. mod_invoice_index.html.twig:432
productId is a JavaScript variable, not a Twig variable. The
Twig expression tried to evaluate it server-side and threw
'Variable productId does not exist'. Build the URL with JS
template literal interpolation instead.
2. mod_client_profile.html.twig:158
guest.client_custom_fields is not a real field in the guest
API response. The current lookup threw 'sequence/mapping is
empty' when no custom fields exist. Default to an empty
mapping, then null, and add a truthy guard.
3. mod_support_ticket.html.twig:60
ticket.rel_type is only populated for tickets linked to an
order; the guest response omits it otherwise. Default to
empty string so the comparison still works.
4. mod_support_ticket.html.twig:80
message.author exposes only name and role for client-side
messages, no email. Pass through |default('') so the avatar
function gets a string and renders its fallback.
* Fix client invoice page for strict_variables
The tab labels on the client invoice index reference
unpaid_invoices.total and paid_invoices.total on lines 33/37
to show count badges, but the corresponding {% set %} calls
sit inside the table bodies on lines 53/85. Under
strict_variables this throws 'Variable unpaid_invoices does
not exist' before the lists are ever fetched.
Move both fetches to the top of the content block so the
variables are defined when first read.
* Allow guest checkout rendering under strict vars
* Rename test helper and tweak test expectations
* Guard optional adapter form fields in gateway and build_form templates
The admin payment gateway edit page and the domain registrar build_form
macro both test element[1].description directly in an if condition,
and both read gateway.config[name] / values[name] without a default.
Built-in adapters (Stripe, PayPalEmail, Custom, the Custom registrar)
define form fields with only a label and sometimes validators or
required_when, so on a fresh install the page throws:
Key 'description' for sequence/mapping with keys 'label, validators'
does not exist in 'mod_invoice_gateway.html.twig' at line 115.
The outer '?? ''' on the inner expression does not help: the
exception is thrown when evaluating the if condition, not when
rendering the markdown. With strict_variables on, that becomes a 500.
Replace each 'if element[1].description' with the short-circuit
'is defined and ...' pattern already used in the same file for
required_when, and drop the now-redundant '?? ''' from the inner
expression. Guard the gateway.config[name] / values[name] reads
with |default('') (or |default([]) for the iterable check) so
fields that have not been configured yet do not throw on a fresh
or upgraded install.
Verified on the dev instance:
/admin/invoice/gateway/{1,2} HTTP 200
/admin/servicedomain/registrar/{1,2} HTTP 200
PayPal gateway form save+reload round-trips correctly.
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|