Next.js 16 static export + PHP 8 API for cPanel/Apache hosting. - Location-aware campus finder (IP estimate, optional precise location, ZIP search) - Three-step Plan Your Visit form with separate email/SMS consent - PHP API: validation, anti-spam, rate limiting, SQLite lead store, Mailchimp and Sinch Engage adapters, idempotent retries via cron - Attribution capture (utm_*, gclid/gbraid/wbraid, fbclid, ttclid, twclid) and consent-aware analytics hooks - Vitest and PHP test suites, cPanel deployment script and docs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
82 lines
2.8 KiB
PHP
82 lines
2.8 KiB
PHP
<?php
|
|
/**
|
|
* Fake Mailchimp + Sinch + Zippopotam + GeoIP endpoints for the PHP test-suite.
|
|
* Started by tests/php/run.php with: php -S 127.0.0.1:8099 tests/php/mock-providers.php
|
|
*
|
|
* Behaviour switches are files in tests/php/.tmp:
|
|
* fail-sinch → Sinch returns HTTP 503
|
|
* fail-mailchimp → Mailchimp returns HTTP 500
|
|
* Every request is appended to tests/php/.tmp/requests.log as JSON.
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
$tmp = __DIR__ . '/.tmp';
|
|
@mkdir($tmp, 0777, true);
|
|
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/';
|
|
$body = file_get_contents('php://input') ?: '';
|
|
file_put_contents("$tmp/requests.log", json_encode([
|
|
'method' => $_SERVER['REQUEST_METHOD'],
|
|
'path' => $path,
|
|
'headers' => ['authorization' => $_SERVER['HTTP_AUTHORIZATION'] ?? null, 'account' => $_SERVER['HTTP_ACCOUNT'] ?? null],
|
|
'body' => json_decode($body, true),
|
|
]) . "\n", FILE_APPEND);
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
// --- Sinch Engage
|
|
if ($path === '/v1/messages') {
|
|
if (file_exists("$tmp/fail-sinch")) {
|
|
http_response_code(503);
|
|
echo json_encode(['message' => 'service unavailable']);
|
|
exit;
|
|
}
|
|
$in = json_decode($body, true);
|
|
$out = [];
|
|
foreach ($in['messages'] ?? [] as $m) {
|
|
$out[] = ['message_id' => 'msg-' . substr(md5($m['destination_number'] . $m['content']), 0, 12), 'status' => 'queued', 'destination_number' => $m['destination_number']];
|
|
}
|
|
http_response_code(202);
|
|
echo json_encode(['messages' => $out]);
|
|
exit;
|
|
}
|
|
|
|
// --- Mailchimp
|
|
if (preg_match('#^/3\.0/lists/([^/]+)/members/([a-f0-9]{32})$#', $path, $m)) {
|
|
if (file_exists("$tmp/fail-mailchimp")) {
|
|
http_response_code(500);
|
|
echo json_encode(['title' => 'Internal Error', 'detail' => 'boom']);
|
|
exit;
|
|
}
|
|
$in = json_decode($body, true);
|
|
echo json_encode(['id' => $m[2], 'email_address' => $in['email_address'] ?? '', 'status' => $in['status_if_new'] ?? 'subscribed', 'merge_fields' => $in['merge_fields'] ?? []]);
|
|
exit;
|
|
}
|
|
if (preg_match('#^/3\.0/lists/[^/]+/members/[a-f0-9]{32}/tags$#', $path)) {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
if (preg_match('#^/3\.0/customer-journeys/#', $path)) {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
// --- Zippopotam
|
|
if (preg_match('#^/us/(\d{5})$#', $path, $m)) {
|
|
if ($m[1] === '00000') {
|
|
http_response_code(404);
|
|
echo '{}';
|
|
exit;
|
|
}
|
|
echo json_encode(['post code' => $m[1], 'places' => [['place name' => 'Norwood', 'state abbreviation' => 'MA', 'latitude' => '42.1945', 'longitude' => '-71.1995']]]);
|
|
exit;
|
|
}
|
|
|
|
// --- ipapi.co
|
|
if (preg_match('#^/([^/]+)/json/?$#', $path)) {
|
|
echo json_encode(['ip' => '203.0.113.9', 'city' => 'Providence', 'region_code' => 'RI', 'latitude' => 41.8240, 'longitude' => -71.4128]);
|
|
exit;
|
|
}
|
|
|
|
http_response_code(404);
|
|
echo json_encode(['error' => 'mock: unknown path ' . $path]);
|