* Harden null handling across core, adapters, and templates
Guard the currency fraction-digit lookup when zlib is unavailable, map unknown GeoIP country codes to incomplete records, tolerate object app options and controllers without a register method, default missing ResellerClub and PayPal IPN fields, and hide server rows when a hosting order has no server.
* Guard non-string GeoIP country codes before enum lookup
* Harden URL, mail, and locale handling
* Keep Sentry hardening mail test offline with unsupported DSN scheme
* Move Sentry regression tests into their module test files
* Drop Sentry issue IDs from regression test names
* Drop Sentry issue ID from Twig cache failure test name
* Drop Sentry issue IDs from test comments
* Stop Twig cache-directory write failures from flooding Sentry
Twig's FilesystemCache throws a raw \RuntimeException when it can't
create or write to the configured template cache directory (data/cache),
which happens whenever a host's file permissions are wrong. That
exception was completely uncaught, so every affected visitor got a raw
500 and every occurrence was reported to Sentry as a code bug even
though it's a host misconfiguration we can't fix from within the app -
this is the same class of noise fixed for the cache backend in #4275,
just from a different code path (~60k events across 4 duplicate-shaped
Sentry issues: FOSSBILLING-EBW/J8N/EWG/EWV).
Box_App::convertCacheWriteFailure() now catches it in both
Box_AppClient::render() and Box_AppAdmin::render(), converts it into a
FOSSBilling\Exception with a new Cache-category error code (5002), and
rethrows anything else unchanged. ErrorPage marks 5002 report:false, so
Sentry stops seeing it while the visitor still gets a proper error page.
* Handle all three Twig cache-write failure messages, not just one
CodeRabbit review on #4277: Twig\Cache\FilesystemCache::write() throws
three different \RuntimeException messages for the same underlying
problem (missing/unwritable data/cache directory) - 'Unable to create
the cache directory', 'Unable to write in the cache directory', and
'Failed to write cache file'. convertCacheWriteFailure() only matched
the first, so the other two still fell through as raw, Sentry-reported
fatals. Match all three, with a regression case for each.
* Don't imply file permissions are the only cause of a cache write failure
CodeRabbit review on #4276 (same code as here): 'Failed to write cache
file' can also come from a full disk, a hit quota, or a read-only
mount, not just a permissions problem - the error message told the
admin to check permissions when that might not be the actual cause.
Broadened the wording to also point at disk space.
* Replace Box_Database usage with Doctrine DBAL
* Harden invoice lookups and DB session setup
* Clean up invoice renewal test mock
* Make DB session timeouts configurable
* ✨ Allow custom recurring billing periods
Products were limited to a fixed set of 7 hardcoded recurring billing
periods (1W/1M/3M/6M/1Y/2Y/3Y), stored as fixed columns on
product_payment. Admins can now configure any number of arbitrary
periods per product (e.g. 45 days, 18 months, 5 years) within
Box_Period's existing valid ranges, while the original 7 periods
remain as pre-filled defaults and one-click quick-add presets.
- Replace product_payment's fixed w/m/q/b/a/bia/tria columns with a
new product_payment_period table (one row per configured period),
migrated via UpdatePatcher::patch98().
- Rework the admin product pricing UI to add/remove billing periods
by quantity + unit (day/week/month/year), in addition to the
existing presets.
- Fix Box_Period's constructor, which only ever accepted a
single-digit quantity (e.g. "9Y" but not "24M" or "45D") despite
its own unit bounds implying otherwise.
- Fix period labels silently disappearing in the client checkout,
admin order form, and admin subscription edit screens for any
period outside Box_Period's 9 presets, by having the API return a
precomputed title per period instead of relying on a second lookup.
- Fix the Stripe adapter dropping the period quantity entirely when
creating recurring prices (e.g. quarterly/biannual products were
silently billed monthly), and reject periods that exceed Stripe's
own interval_count caps instead of mis-billing them.
Closes#4098
* Address review feedback on custom billing periods
- Make patch98's period migration INSERT idempotent with ON DUPLICATE
KEY UPDATE, so a rerun after a partial failure doesn't hit the
(product_payment_id, code) unique constraint.
- Order ProductPayment::$periods by sortOrder so the admin's edit
order is preserved on reload, matching the pattern already used by
ServiceDownloadable's ordered collection.
- Wrap the Box_Period parsing failure in
getEnabledProductPaymentPeriod() so a malformed/missing period
raises FOSSBilling\InformationException instead of a raw exception,
matching applyPricingToProductPayment()'s handling.
- Fix the Stripe adapter reusing a wrong-cadence price: Stripe's price
list filter only supports 'interval', not 'interval_count', so the
existing-price match now also compares interval_count explicitly.
- Stop the admin pricing form from resurrecting default periods an
admin had deliberately removed - defaults are now only used when a
product has no configured periods at all.
- Drop the redundant product_payment_id_idx index, since the unique
key on (product_payment_id, code) already covers lookups on
product_payment_id via its leftmost prefix.
* Fix quantity validation and i18n in the add-period JS
- Use Number() + Number.isInteger() instead of parseInt() to validate
the custom period quantity, so a fractional value like "5.7" is
rejected instead of silently truncated to 5.
- Replace the English-only '(s)' suffix hack for pluralizing the unit
label with translated data-singular/data-plural attributes on each
unit option, selected based on quantity.
* Improve type safety, auth checks and PHPStan fixes (#3250)
* Improve type safety, auth checks and PHPStan fixes
* Remove @phpstan-ignore annotations
* Improve type safety, auth checks and PHPStan fixes
* Remove @phpstan-ignore annotations
* Remove redundant @phpstan-ignore comment
* Improve code robustness and reliability (#3248)
* Harden code against null/undefined values
* Update dependency lockfiles
* Sanitize ticket input and use IDs in email list
* Restructure tests and add test utilities
* Move E2E tests to `Library\E2E` namespace
* Update `php-build` action ref in workflows
* Switch tests to Pest and add test scaffolding
* Refactor E2E tests to Pest-style tests
m
Skip E2E tests when env not configured
* Refactor Unit tests and update test config
Add test datasets and refine test helpers
* Add DEBUG guard, fix API JSON decode, update tests
* Refactor tests: add types and Mockery expectations
* Adjust tests, phpstan config, and composer script
* Instantiate test APIs locally, remove beforeEach
* Refactor runtime checks, types & logging
* Apply PHP Code Styling Changes
m
* Add PHPStan ignores, test renames & small fixes
* Enable Pest tests in CI workflow
* Remove legacy tests; add new unit tests/config
* Avoid undefined index in fingerprint comparison
* Ignore properties absent in both fingerprints
* Remove public_ticket_create API method
* Add Fingerprint unit tests
* Remove em dash from fingerprint test name
* Switch to Pest; assorted bug fixes & refactors
* Prefer getDi() and guard DEBUG define
* Refactor tests: DBAL, Antispam rename, helpers
* Refactor email tests to use Doctrine entities
* Update tests, use DBAL mocks & improve Formbuilder admin
* Validate plan_id and update unit tests
* Replace error_log with DI logger and update tests
* Expect db->store in Invoice Service test
* Use DI logger instead of error_log; fix tests
* Fix typo in Hook Service test
* Fix Staff test spelling fixture
* Complete Pest test migration
* Fix Product test spelling fixtures
* Fix Product test spelling fixtures
* Run live tests with Pest and load helpers
* Migrate tests to ApiClient; remove legacy APIHelper
* Fix Email test spelling
* Remove unused PHPUnit bridge
* Organize Cypress end-to-end tests
* Document Pest test directory root
* Remove noisy Pest comments