Files
FOSSBilling/tests/APIHelper.php
T
Adam Daley 53eb4f2fca Refactor Twig environments and extensions (#2752)
* Replace Custom Twig components

Replace custom Twig components with native Twig components, remove unused custom Twig components, and move functionality to macros where appropriate.

m

Update partial_extensions.html.twig

* Update Twig components and migrate to FOSSBilling namespace

* Update Currency module...

* Refactor Twig environment config and loading

* Switch CSRF to cookie-based double-submit

* Add Api Twig extension and restore widget/svg helpers

* Cast language flags; improve JSON parsing

* Add markdown DI service and use in Admin API

* Refactor Twig extensions and markdown, simplify APIs

* Add sandboxed email Twig env, update render APIs

* Fix pagination URL usage and menu class

* Fix HTML markup in email templates

* Use macro for avatars and status badges

* Fix Twig spacing and pagination URL filter order

* Replace Custom Twig components

Replace custom Twig components with native Twig components, remove unused custom Twig components, and move functionality to macros where appropriate.

m

Update partial_extensions.html.twig

* Update Twig components and migrate to FOSSBilling namespace

* Refactor Twig environment config and loading

* Add Api Twig extension and restore widget/svg helpers

* Cast language flags; improve JSON parsing

* Add markdown DI service and use in Admin API

* Refactor Twig extensions and markdown, simplify APIs

* Fix pagination URL usage and menu class

* Fix Twig spacing and pagination URL filter order

* Use `current_theme` global and refactor `period_title`

* Allow markdown and date in email Twig sandbox

* Update email template examples

* Fix Twig URLs, quoting, and logo alt text

* Fix Twig templates: URLs, i18n, search params

* Use `templates/*` directories for module templates

* Tighten Twig sandbox and add tests

* Add Twig environment and template docs to `AGENTS.md`

* Use Symfony Filesystem/Finder and assorted fixes

* Fix ticket reply link fragment concatenation

* Close missing divs in dashboard template

* Use detailed `toApiArray` for client

* Fix module name resolution for email templates

* Regenerate `composer.lock`

* Use `markdown_to_html` filter in admin macro

* Handle null price/quantity and rename test method

* Use `Tools::isHTTPS()` for HTTPS checks

* Add 'area' param to `url` filter and update templates

* Use client area URLs in admin templates

* Use client area and guest API URLs in templates

* Remove client-side invoice delete feature

* Create Twig env via `TwigFactory` for PDF rendering

* Add embedded orderbutton JS and update render

* Add 'srclink' to cspell allowlist

* Use `renderEmailTplString` for email rendering

* Bump twig/markdown-extra to v3.24

* Use `markdown_to_html` for privacy policy

* Escape API JSON, handle null IP, client URLs

* Remove initial admin creation flow

* Handle HTTP failures and make tests robust

* Use `api_url` helper in admin templates

* Use `url` filter for footer link 5

* Add patch53 to remove legacy html templates

* Bump setting last_patch to 53

* Add patch54 to restore legacy email templates

* Use renderEmailTplString for email templating

* Remove redundant else/endif in order template

* Add file-hash cache buster for asset URLs

* Remove raw Twig filter and handle missing modules dir

* Return '-' for future timestamps

* Register and reuse TwigFactory and add CSRF cookie

* Adjust API URL helper and add Twig error handling

* Update composer.lock and package-lock.json

* Add twig_factory to ServiceTest DI

* Use url filter for link_page in template

* Composer - update deps and add markdown-extra

* Remove docblocks and inline comments in `TwigFactory`

* Add patches 53-55 and update `last_patch`

* Add `has_permission` Twig function; exclude `href`

* Remove duplicate network interface form

* Guard removal of legacy dirs; add network input

* Handle null category descriptions in template

* Add Twig adapter env and sandbox policy

* Add 'striptags' to cspell word list

* Update composer.lock (deps updated/removed)

* Sanitize custom payment adapter templates

* Remove redundant legacy API tests

* Bump `last_patch` setting to 56

* Coalesce null before `markdown_to_html`

* Move update patcher to system CLI command

* Remove stray `endif` from recaptcha macro

* Update composer.lock

* Fix extra closing brace in Api ServiceTest

* Fix test errors from main merge

- Remove testGetRequestCount tests (method moved to RateLimiter)
- Remove testGetDi from Staff and System GuestTest (protected method)
- Fix IP error message in testLoginCheckIpException
- Add rate_limiter mock to Staff GuestTest tests that need it

* Fix remaining Staff GuestTest issues

- Add extension service mock for captcha check
- Fix error message from 'User not found' to 'Admin not found'

* Fix Staff GuestTest: update query and add logger mock

* Fix Staff GuestTest: set admin status to active for password reset flow

* Fix stale API calls and inverted required condition

- Notification: replace removed system_string_render with direct output
- Antispam settings: replace |alink/|link with |url/|api_url filters
- build_form macro: fix inverted required condition for non-textarea inputs

* Sanitize JS/data URIs and disable buttons by permission

* Use client login/profile links in support emails

* Encode fb-api JSON and tighten URL sanitization

* Prefer session auth for API key methods

* Remove stray endif and fix template formatting

* Add CSRF token to CSV exports; fix template

* Remove stray endif in currency template

* Make invoice PDF generation return Response

* Fix Twig filters, templates, and form handling

* Remove readonly from anonymous class in test

* Inline DebugBar CSS/JS in renderHead

* Add Bootstrap Tab to global bootstrap

* Capitalize template UI strings

* Use CKEditor API for reply quotes

* Remove WYSIWYG module and move CKEditor to themes

* Bump `last_patch` in install SQL to 59

* Remove Spamchecker module

* Run domain management script only for active orders

* Standardize UI text capitalization & minor fixes

* Remove Spamchecker legacy tests; fix period test

* Rename Spamchecker tests to Antispam

* Restore antispam config in tests; remove fixture

* Refactor Antispam Guest tests for config checks

* Update theme build docs; remove Wysiwyg module

* Remove spamchecker from cspell and tweak comment

* Add spamchecker to cspell word list

* Capitalize 'Update Graphs' button label
2026-05-06 21:02:16 +01:00

132 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace APIHelper;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
class Request
{
/**
* Handles making a request to the FOSSBilling server.
*
* @param string $endpoint the API endpoint to call (Example: `guest/system/company`)
* @param string $method set to POST to have cURL make a POST request to the server
* @param array $payload when using POST, this array will be sent as the POST fields
* @param string|null $apiKey (optional) the API key to authenticate with
* @param string|null $baseUrl (optional) the base instance URL to make requests against (Example: `http://localhost/`)
*/
public static function makeRequest(string $endpoint, array $payload = [], ?string $role = null, ?string $apiKey = null, string $method = 'POST', ?string $baseUrl = null): Response
{
$cookie = Path::join(sys_get_temp_dir(), 'cookie.txt');
if (!$role) {
$role = str_starts_with($endpoint, 'admin') ? 'admin' : 'client';
}
if (!$apiKey) {
$apiKey = getenv('TEST_API_KEY');
}
if (!$baseUrl) {
$baseUrl = getenv('APP_URL');
}
$ch = curl_init($baseUrl . 'api/' . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_USERPWD, "$role:$apiKey");
if (strcasecmp($method, 'POST') === 0) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
}
$output = curl_exec($ch);
if ($output === false) {
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
throw new \RuntimeException('cURL request failed: ' . $error . ' (HTTP ' . $httpCode . ')');
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return new Response($httpCode, $output);
}
public static function resetCookies(): void
{
$filesystem = new Filesystem();
$filesystem->remove(Path::join(sys_get_temp_dir(), 'cookie.txt'));
}
}
class Response
{
private array $decodedResponse;
public function __construct(private readonly int $code, private readonly string $rawResponse)
{
$decoded = json_decode($this->rawResponse, true);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
$preview = strlen($this->rawResponse) > 500 ? substr($this->rawResponse, 0, 500) . '...' : $this->rawResponse;
throw new \RuntimeException('Invalid JSON response: ' . json_last_error_msg() . ' (HTTP ' . $this->code . '). Response: ' . $preview);
}
$this->decodedResponse = $decoded;
}
public function getHttpCode(): int
{
return $this->code;
}
public function getResponse(): array
{
return $this->decodedResponse;
}
public function getRawResponse(): string
{
return $this->rawResponse;
}
public function wasSuccessful(): bool
{
return !empty($this->decodedResponse)
&& array_key_exists('error', $this->decodedResponse)
&& empty($this->decodedResponse['error']);
}
public function getErrorMessage(): string
{
return $this->decodedResponse['error']['message'] ?? 'None';
}
public function getErrorCode(): int
{
return intval($this->decodedResponse['error']['code'] ?? 0);
}
public function getError(): string
{
return $this->getErrorMessage() . ' (Code ' . $this->getErrorCode() . ')';
}
public function generatePHPUnitMessage(): string
{
return 'The API request failed with the following message: ' . $this->getError();
}
public function getResult(): mixed
{
return $this->decodedResponse['result'] ?? '';
}
}