Files
global-autoresponder/exec/lib/DirectAdminVacationApi.php
T
2026-06-02 22:23:27 +02:00

187 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
class DirectAdminVacationApi
{
/** @var callable|null */
private $request;
private string $daBinary = '/usr/local/directadmin/directadmin';
public function __construct(string $daBinary = '/usr/local/directadmin/directadmin', ?callable $request = null)
{
$this->daBinary = $daBinary;
$this->request = $request;
}
public function directAdminBinary(): string
{
return $this->daBinary;
}
/** @param array<string,mixed> $rule @return array<string,string> */
public function buildSavePayload(string $email, array $rule, bool $exists = true): array
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid mailbox email');
}
[$local, $domain] = explode('@', strtolower($email), 2);
[$startDate, $startTime] = $this->splitPeriod((string)($rule['start_value'] ?? ''));
[$endDate, $endTime] = $this->splitPeriod((string)($rule['end_value'] ?? ''));
[$startYear, $startMonth, $startDay] = explode('-', $startDate);
[$endYear, $endMonth, $endDay] = explode('-', $endDate);
$subjectPrefix = (string)($rule['subject_prefix'] ?? $rule['subject'] ?? '');
$replyFrequency = !empty($rule['reply_every_message']) ? '0' : (string)max(0, (int)($rule['repeat_minutes'] ?? 1440));
return [
'action' => $exists ? 'modify' : 'create',
'domain' => $domain,
'user' => $local,
'email' => $email,
'text' => (string)($rule['body'] ?? ''),
'subject_prefix' => $subjectPrefix,
'subject' => $subjectPrefix,
'reply_frequency' => $replyFrequency,
'reply_once_time' => $replyFrequency,
'starttime' => $startTime,
'startyear' => $startYear,
'startmonth' => $startMonth,
'startday' => $startDay,
'endtime' => $endTime,
'endyear' => $endYear,
'endmonth' => $endMonth,
'endday' => $endDay,
'create' => $exists ? '' : 'Create',
];
}
/** @param array<string,mixed> $rule */
public function saveVacation(string $owner, string $email, array $rule, bool $exists): void
{
$this->requestAsUser($owner, 'CMD_API_EMAIL_VACATION', $this->buildSavePayload($email, $rule, $exists));
}
public function vacationExists(string $owner, string $email): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid mailbox email');
}
[$local, $domain] = explode('@', strtolower($email), 2);
return in_array($local, $this->listVacations($owner, $domain), true);
}
/** @return string[] */
public function listVacations(string $owner, string $domain): array
{
if (!preg_match('/^[A-Za-z0-9.-]+$/', $domain)) {
throw new InvalidArgumentException('Invalid domain');
}
$response = $this->requestAsUser($owner, 'CMD_API_EMAIL_VACATION', ['domain' => strtolower($domain)]);
$users = [];
foreach (explode('&', trim($response)) as $pair) {
if ($pair === '' || !str_contains($pair, '=')) {
continue;
}
[$key] = explode('=', $pair, 2);
$local = strtolower(urldecode($key));
if (preg_match('/^[A-Za-z0-9._+-]+$/', $local)) {
$users[] = $local;
}
}
sort($users);
return array_values(array_unique($users));
}
public function deleteVacation(string $owner, string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid mailbox email');
}
[$local, $domain] = explode('@', strtolower($email), 2);
$this->requestAsUser($owner, 'CMD_API_EMAIL_VACATION', [
'action' => 'delete',
'domain' => $domain,
'select0' => $local,
]);
}
/** @return array{0:string,1:string} */
private function splitPeriod(string $value): array
{
if (!preg_match('/^([0-9]{4}-[0-9]{2}-[0-9]{2})\|(morning|afternoon|evening)$/', $value, $m)) {
throw new InvalidArgumentException('DirectAdmin API backend requires directadmin_period values');
}
return [$m[1], $m[2]];
}
/** @param array<string,string> $payload */
private function requestAsUser(string $owner, string $endpoint, array $payload): string
{
if (!preg_match('/^[A-Za-z0-9._-]+$/', $owner)) {
throw new InvalidArgumentException('Invalid DirectAdmin owner');
}
if ($this->request !== null) {
$result = call_user_func($this->request, $owner, $endpoint, $payload);
if ($result === false) {
throw new RuntimeException('DirectAdmin API request failed');
}
return (string)$result;
}
$baseUrl = $this->apiUrl($owner);
$url = rtrim($baseUrl, '/') . '/' . $endpoint;
$args = ['curl', '-fsS', '-X', 'POST'];
foreach ($payload as $key => $value) {
$args[] = '--data-urlencode';
$args[] = $key . '=' . $value;
}
$args[] = $url;
return $this->run($args, '');
}
private function apiUrl(string $owner): string
{
$output = $this->run([$this->resolveDirectAdminBinary(), 'api-url', '--user=' . $owner], '');
$url = trim($output);
if ($url === '' || !preg_match('#^https?://#', $url)) {
throw new RuntimeException('Cannot determine DirectAdmin API URL for user');
}
return $url;
}
private function resolveDirectAdminBinary(): string
{
$candidates = array_values(array_unique([
$this->daBinary,
'/usr/local/directadmin/directadmin',
'/usr/bin/da',
]));
foreach ($candidates as $candidate) {
if ($candidate !== '' && is_executable($candidate)) {
return $candidate;
}
}
throw new RuntimeException('DirectAdmin binary is not executable. Checked: ' . implode(', ', $candidates));
}
/** @param string[] $args */
private function run(array $args, string $stdin): string
{
if (($args[0] ?? '') !== '' && str_contains($args[0], '/') && !is_executable($args[0])) {
throw new RuntimeException('DirectAdmin API command is not executable: ' . $args[0]);
}
$process = proc_open($args, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes);
if (!is_resource($process)) {
throw new RuntimeException('Cannot start DirectAdmin API command');
}
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]) ?: '';
$stderr = stream_get_contents($pipes[2]) ?: '';
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($process);
if ($code !== 0) {
throw new RuntimeException('DirectAdmin API command failed: ' . trim($stderr));
}
return $stdout;
}
}