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); return [ 'action' => $exists ? 'modify' : 'create', 'domain' => $domain, 'user' => $local, 'email' => $email, 'text' => (string)($rule['body'] ?? ''), 'subject' => (string)($rule['subject'] ?? ''), '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 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): void { 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; } $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; $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; } }