isSystemFlag($action['flag'])) { $flag = SieveUtils::escapeString($action['flag']); } else { $flag = SieveUtils::escapeString($this->sanitizeFlag($action['flag'])); } $actions[] = sprintf('addflag "%s";', $flag); } if ($action['type'] === 'keep') { $actions[] = 'keep;'; } if ($action['type'] === 'stop') { $actions[] = 'stop;'; } } if (count($tests) > 1) { $ifTest = sprintf('%s (%s)', $filter['operator'], implode(', ', $tests)); } else { $ifTest = $tests[0]; } $actions = array_map( static fn ($action) => "\t" . $action, $actions ); $ifBlock = sprintf( "if %s {\r\n%s\r\n}", $ifTest, implode(self::SIEVE_NEWLINE, $actions) ); $commands[] = $ifBlock; } $lines = []; $extensions = array_unique($extensions); if (count($extensions) > 0) { $lines[] = self::SEPARATOR; $lines[] = 'require ' . SieveUtils::stringList($extensions) . ';'; $lines[] = self::SEPARATOR; } /* * Using implode("\r\n", $lines) may introduce an extra newline if the original script already ends with one. * There may be a cleaner solution, but I couldn't find one that works seamlessly with Filter and Autoresponder. * Feel free to give it a try! */ if (str_ends_with($untouchedScript, self::SIEVE_NEWLINE . self::SIEVE_NEWLINE)) { $untouchedScript = substr($untouchedScript, 0, -2); } $lines[] = $untouchedScript; if (count($filters) > 0) { $lines[] = self::SEPARATOR; $lines[] = self::DATA_MARKER . json_encode($this->sanitizeDefinition($filters), JSON_THROW_ON_ERROR); array_push($lines, ...$commands); $lines[] = self::SEPARATOR; } return implode(self::SIEVE_NEWLINE, $lines); } private function sanitizeFlag(string $flag): string { try { return $this->imapFlag->create($flag); } catch (ImapFlagEncodingException) { return 'placeholder_for_invalid_label'; } } private function sanitizeDefinition(array $filters): array { return array_map(static function ($filter) { unset($filter['accountId'], $filter['id']); $filter['tests'] = array_map(static function ($test) { unset($test['id']); return $test; }, $filter['tests']); $filter['actions'] = array_map(static function ($action) { unset($action['id']); return $action; }, $filter['actions']); $filter['priority'] = (int)$filter['priority']; return $filter; }, $filters); } private function isSystemFlag(string $flag): bool { $flags = [ \Horde_Imap_Client::FLAG_ANSWERED, \Horde_Imap_Client::FLAG_DELETED, \Horde_Imap_Client::FLAG_DRAFT, \Horde_Imap_Client::FLAG_FLAGGED, \Horde_Imap_Client::FLAG_RECENT, \Horde_Imap_Client::FLAG_SEEN, ]; // Check is done in lowercase to keep the notation from the RFC (e.g. \Seen) return in_array(strtolower($flag), $flags, true); } }