mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-11-03 02:27:21 +09:00
* 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>
95 lines
3.2 KiB
PHP
95 lines
3.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Copyright 2022-2026 FOSSBilling
|
|
* SPDX-License-Identifier: Apache-2.0.
|
|
*
|
|
* @copyright FOSSBilling (https://www.fossbilling.org)
|
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Support;
|
|
|
|
use Twig\EmbedNode;
|
|
use Twig\Environment;
|
|
use Twig\Node\Expression\ArrayExpression;
|
|
use Twig\Node\Expression\AssignNameExpression;
|
|
use Twig\Node\Expression\ConstantExpression;
|
|
use Twig\Node\Expression\Variable\ContextVariable;
|
|
use Twig\Node\Expression\Variable\TemplateVariable;
|
|
use Twig\Node\IncludeNode;
|
|
use Twig\Node\Node;
|
|
use Twig\NodeVisitor\NodeVisitorInterface;
|
|
|
|
/**
|
|
* Walks a parsed Twig AST and collects the names of every context variable
|
|
* (e.g. `{{ foo }}`, `{% if bar %}`, `{% set baz = ... %}`) referenced anywhere
|
|
* in the template.
|
|
*
|
|
* Also collects the keys of `{% embed %}` and `{% include %}` `with { ... }`
|
|
* hashes, since those keys become variables inside the embedded/included
|
|
* template's scope.
|
|
*
|
|
* Used by StrictTemplateRenderer to pre-populate the render context with
|
|
* permissive stubs so that partials (which expect specific parent-passed
|
|
* variables) render successfully in isolation. Any actual rendering error
|
|
* raised despite every referenced variable being stubbed is a real bug.
|
|
*/
|
|
final class VariableCollectorVisitor implements NodeVisitorInterface
|
|
{
|
|
/** @var array<string, true> Set of discovered variable names. */
|
|
public array $variables = [];
|
|
|
|
public function enterNode(Node $node, Environment $env): Node
|
|
{
|
|
if ($node instanceof ContextVariable || $node instanceof TemplateVariable) {
|
|
$name = $node->getAttribute('name');
|
|
if (is_string($name) && $name !== '' && !str_starts_with($name, '_')) {
|
|
$this->variables[$name] = true;
|
|
}
|
|
} elseif ($node instanceof AssignNameExpression) {
|
|
$name = $node->getAttribute('name');
|
|
if (is_string($name) && $name !== '' && !str_starts_with($name, '_')) {
|
|
$this->variables[$name] = true;
|
|
}
|
|
} elseif ($node instanceof EmbedNode || $node instanceof IncludeNode) {
|
|
// The `with { foo: bar }` block on embed/include passes its keys
|
|
// as variables into the embedded template. Stub those too.
|
|
$variables = $node->hasNode('variables') ? $node->getNode('variables') : null;
|
|
if ($variables instanceof ArrayExpression) {
|
|
foreach ($variables->getKeyValuePairs() as $pair) {
|
|
$key = $pair['key'];
|
|
if ($key instanceof ConstantExpression) {
|
|
$k = $key->getAttribute('value');
|
|
if (is_string($k) && $k !== '' && !str_starts_with($k, '_')) {
|
|
$this->variables[$k] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $node;
|
|
}
|
|
|
|
public function leaveNode(Node $node, Environment $env): ?Node
|
|
{
|
|
return $node;
|
|
}
|
|
|
|
public function getPriority(): int
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
public function getVariableNames(): array
|
|
{
|
|
return array_keys($this->variables);
|
|
}
|
|
}
|