daBinary = $daBinary; $this->request = $request; } /** @param array $rule @return array */ 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 $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 $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->daBinary, '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; } /** @param string[] $args */ private function run(array $args, string $stdin): string { $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; } }