/api/3`, header `Api-Token`). * * - POST /contact/sync {contact: {email, firstName, lastName, fieldValues: [{field, value}]}} → contact.id * (`activecampaign_fields` keys are custom field ids) * - POST /contactLists {contactList: {list, contact, status: 1}} * - tags: numeric = tag id, otherwise looked up by name (GET /tags?search=) and created when missing * (POST /tags), then POST /contactTags {contactTag: {contact, tag}} * - lists: GET /lists?limit=100 */ final class ActiveCampaign extends EmailProvider { public function id(): string { return 'activecampaign'; } public function label(): string { return 'ActiveCampaign'; } public function configured(): bool { return $this->base() !== '' && Credentials::has('activecampaign_api_key'); } public function base(): string { $url = Credentials::get('activecampaign_url'); return preg_match('#^https://[^\s/]+#i', $url) ? rtrim($url, '/') . '/api/3' : ''; } /** @return array */ private function headers(): array { return ['Api-Token' => Credentials::get('activecampaign_api_key')]; } public function lists() { if (!$this->configured()) { return new WP_Error('zadex_integration_not_configured', 'ActiveCampaign URL or API key missing.'); } $r = Http::request('GET', $this->base() . '/lists?limit=100', ['headers' => $this->headers()]); return $r instanceof WP_Error ? $r : self::options($r['json']['lists'] ?? [], 'id', 'name'); } public function subscribe(array $contact) { $data = ['email' => $contact['email']]; if ($contact['first_name'] !== '') { $data['firstName'] = $contact['first_name']; } if ($contact['last_name'] !== '') { $data['lastName'] = $contact['last_name']; } foreach ($contact['fields'] as $field => $value) { $data['fieldValues'][] = ['field' => (string) $field, 'value' => $value]; } $r = Http::json('POST', $this->base() . '/contact/sync', ['contact' => $data], $this->headers()); if ($r instanceof WP_Error) { return $r; } $id = isset($r['json']['contact']['id']) && is_scalar($r['json']['contact']['id']) ? (string) $r['json']['contact']['id'] : ''; if ($id === '') { return new WP_Error('zadex_integration_bad_response', 'ActiveCampaign did not return a contact id.'); } $l = Http::json('POST', $this->base() . '/contactLists', ['contactList' => ['list' => $contact['list'], 'contact' => $id, 'status' => 1]], $this->headers()); if ($l instanceof WP_Error) { return $l; } foreach ($contact['tags'] as $tag) { $tag_id = $this->tag_id($tag); if ($tag_id instanceof WP_Error) { return $tag_id; } $t = Http::json('POST', $this->base() . '/contactTags', ['contactTag' => ['contact' => $id, 'tag' => $tag_id]], $this->headers()); if ($t instanceof WP_Error) { return $t; } } return true; } /** @return string|WP_Error */ private function tag_id(string $tag) { if (ctype_digit($tag)) { return $tag; } $found = Http::request('GET', $this->base() . '/tags?search=' . rawurlencode($tag), ['headers' => $this->headers()]); if ($found instanceof WP_Error) { return $found; } foreach ((array) ($found['json']['tags'] ?? []) as $row) { if (is_array($row) && isset($row['tag'], $row['id']) && strcasecmp((string) $row['tag'], $tag) === 0) { return (string) $row['id']; } } $created = Http::json('POST', $this->base() . '/tags', ['tag' => ['tag' => $tag, 'tagType' => 'contact', 'description' => '']], $this->headers()); if ($created instanceof WP_Error) { return $created; } $id = $created['json']['tag']['id'] ?? ''; return is_scalar($id) && (string) $id !== '' ? (string) $id : new WP_Error('zadex_integration_bad_response', 'ActiveCampaign did not return a tag id.'); } }