connectors

object

%Connector

/phlo/resources/connectors/Connector.phlo

Base class for API connectors: credentials, JSON requests, retries, pagination and a normalized result contract

Make one with Connector::make(); it reads its own section from %creds, so keys live in data/creds.ini or in PHLO__Section__key in the environment and never in your code. Every call answers in the same shape, ok with status and data or ok false with error, and nothing is thrown, so test ->ok rather than catching. Raise retries above zero to let GET, HEAD and QUERY back off and try again on 429 and 5xx; writes are never retried, because a repeated POST would book twice.

apiconnectorhttprestbase
const

Connector :: section

line 11
在 Connector 中定义一个部分,允许组织相关的组件和功能。
void
const

Connector :: api

line 12
定义一个用于API交互的连接器,允许不同服务之间无缝通信。
void
method

%Connector -> __construct (?array $config = null)

line 14
使用可选的配置设置初始化Connector实例。如果未提供配置,它将尝试从预定义的部分加载凭据。
if ($config === null){
	$section = static::section
	$creds = $section ? %creds->{$section} : null
	$config = $creds ? (array)$creds->toArray : []
}
$this->config = $config
$this->timeout = 15
$this->retries = 0
static

Connector :: make (?array $config = null):static

line 25
使用提供的配置创建 Connector 类的新实例。
new static($config)
method

%Connector -> base:string

line 27
访问Connector的基础API,允许与其核心功能进行交互。
static::api
method

%Connector -> headers:array

line 28
此属性检索与 Connector 相关的头部信息。
[]
static

Connector :: fields:array

line 30
Connector::$fields 是一个静态属性,包含 Connector 类的字段定义数组。
[]
method

%Connector -> configured (...$keys):bool

line 32
检查连接器的配置中是否配置了所有指定的键。如果所有键都存在,则返回true,否则返回false。
foreach ($keys AS $key){
	if (($this->config[$key] ?? void) === void) return false
}
return true
method

%Connector -> missing (...$keys):?obj

line 39
检查指定的凭据是否已配置,如果已配置则返回null;否则,触发一个失败,指明缺少哪些凭据。
return $this->configured(...$keys) ? null : static::fail(static::section.' credentials not configured ('.implode(', ', $keys).')')
static

Connector :: bearer ($token):string

line 43
Connector::$bearer 属性保存格式化的包含 Bearer 令牌的授权头值,用于 API 请求。
'Authorization: Bearer '.$token
static

Connector :: basic ($user, $pass):string

line 44
通过将提供的用户名和密码编码为base64格式来生成Basic Authorization头。
'Authorization: Basic '.base64_encode($user.colon.$pass)
static

Connector :: build (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):array

line 46
if ($query) $url .= (str_contains($url, qm) ? '&' : qm).http_build_query($query)
$body = null
if ($json !== null){
	$body = is_string($json) ? $json : json_encode($json, jsonFlat)
	$headers[] = 'Content-Type: application/json'
}
elseif ($form !== null){
	$body = is_string($form) ? $form : http_build_query($form)
	$headers[] = 'Content-Type: application/x-www-form-urlencoded'
}
$headers[] = 'Accept: application/json'
return ['method' => strtoupper($method), 'url' => $url, 'headers' => $headers, 'body' => $body]
static

Connector :: ok ($data, int $status = 200):obj

line 61
Connector::$ok 属性包含一个对象,该对象具有布尔 'ok' 值、'status' 和 'data' 属性。
obj(ok: true, status: $status, data: $data)
static

Connector :: fail ($error, int $status = 0):obj

line 62
Connector::$fail 属性表示失败状态,包含一个对象,其中 'ok' 设置为 false,并包含 'status' 和 'error' 详细信息。
obj(ok: false, status: $status, error: $error)
static

Connector :: errorMessage ($data, string $raw, int $status):string

line 64
Connector::$errorMessage 从给定的数据对象中检索错误消息,检查各种属性以获取错误的字符串表示,如果未找到错误,则默认为格式化的 HTTP 状态消息。
if (is_object($data)){
	if (isset($data->error->message)) return (string)$data->error->message
	if (isset($data->error) && is_string($data->error)) return $data->error
	if (isset($data->message)) return (string)$data->message
	if (isset($data->errors)){
		$errors = $data->errors
		if (is_string($errors)) return $errors
		if (is_array($errors)) return is_string($errors[0] ?? null) ? $errors[0] : json_encode($errors)
		if (is_object($errors)) return json_encode($errors)
	}
}
return $raw !== void ? $raw : 'HTTP '.$status
static

Connector :: parse ($raw, int $status = 200):obj

line 79
将原始输入字符串解析为JSON对象,根据HTTP状态码返回成功响应或错误消息。
$raw = (string)$raw
$data = $raw === void ? null : json_decode($raw)
if ($status < 200 || $status >= 300) return static::fail(static::errorMessage($data, $raw, $status), $status)
return obj(ok: true, status: $status, data: $data ?? $raw)
static

Connector :: retryable ($method, int $status):bool

line 86
in_array($method, ['GET', 'HEAD', 'QUERY']) && ($status === 429 || $status >= 500)
static

Connector :: backoff (int $attempt, $response):int

line 88
Connector::$backoff 属性根据响应中的 'retry-after' 头计算以微秒为单位的退避时间,最大限制为 30 秒或默认值乘以尝试次数。
$after = (int)($response->headers['retry-after'] ?? 0)
return $after > 0 ? min($after, 30) * 1000000 : 200000 * $attempt
method

%Connector -> dispatch (array $req):obj

line 97
Retries only what retryable() allows, and waits as long as the server asked for.
$method = $req['method']
$body = $req['body']
$attempt = 0
$response = null
while (true){
	try {
		if ($method === 'GET') $raw = HTTP($req['url'], $req['headers'], cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'DELETE') $raw = HTTP($req['url'], $req['headers'], DELETE: true, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'PUT') $raw = HTTP($req['url'], $req['headers'], PUT: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'PATCH') $raw = HTTP($req['url'], $req['headers'], PATCH: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'QUERY') $raw = HTTP($req['url'], $req['headers'], QUERY: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		else $raw = HTTP($req['url'], $req['headers'], POST: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
	}
	catch (\Throwable $e){
		return static::fail($e->getMessage(), 0)
	}
	$status = $response->status ?? 0
	if (($status >= 200 && $status < 300) || !static::retryable($method, $status) || $attempt >= $this->retries) break
	usleep(static::backoff(++$attempt, $response))
}
$result = static::parse($raw, $status)
$result->headers = $response->headers ?? []
return $result
method

%Connector -> request (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):obj

line 123
使用指定的方法、URL、查询参数、头部以及可选的JSON或表单数据发送HTTP请求。
if (!str_starts_with($url, 'http')) $url = rtrim((string)$this->base, slash).slash.ltrim($url, slash)
$headers = array_merge((array)$this->headers, $headers)
return $this->dispatch(static::build($method, $url, $query, $headers, $json, $form))
method

%Connector -> get (string $url, ?array $query = null, array $headers = []):obj

line 129
向指定的URL发送GET请求,带有可选的查询参数和头部信息。
$this->request('GET', $url, query: $query, headers: $headers)
method

%Connector -> post (string $url, mixed $json = null, array $headers = []):obj

line 130
向指定的URL发送带有可选JSON数据和头部的POST请求。
$this->request('POST', $url, headers: $headers, json: $json)
method

%Connector -> put (string $url, mixed $json = null, array $headers = []):obj

line 131
向指定的URL发送PUT请求,附带可选的JSON数据和头部信息。
$this->request('PUT', $url, headers: $headers, json: $json)
method

%Connector -> patch (string $url, mixed $json = null, array $headers = []):obj

line 132
向指定的URL发送PATCH请求,带有可选的JSON数据和头部信息。
$this->request('PATCH', $url, headers: $headers, json: $json)
method

%Connector -> query (string $url, mixed $json = null, array $headers = []):obj

line 133
$this->request('QUERY', $url, headers: $headers, json: $json)
method

%Connector -> del (string $url, array $headers = []):obj

line 134
向指定的URL发送DELETE请求,并可选地附加头部信息。
$this->request('DELETE', $url, headers: $headers)
method

%Connector -> form (string $url, array $fields, array $headers = []):obj

line 135
向指定的URL发送带有给定表单字段和头部的POST请求。
$this->request('POST', $url, headers: $headers, form: $fields)
method

%Connector -> paginate (string $url, callable $extract, ?array $query = null, string $param = 'page', int $start = 1, int $max = 0):array

line 137
从指定的URL获取分页数据,使用提供的可调用函数提取项目,并在达到最大数量或没有更多数据可用时进行累积。
$items = []
$page = $start
while (true){
	$res = $this->get($url, ($query ?? []) + [$param => $page])
	if (!$res->ok) break
	$batch = $extract($res->data)
	if (!$batch) break
	foreach ($batch AS $item) $items[] = $item
	if ($max && count($items) >= $max) break
	$page++
}
return $items
object

%EBoekhouden

/phlo/resources/connectors/finance/EBoekhouden.phlo

e-Boekhouden.nl connector: session auth from an API token, relations and sales invoices

Authenticates with a session rather than a token per call: the first call trades api_token for one, later calls in the same request reuse it, and a new request starts over. Sessions are limited, so gather the work you need per request instead of making a connector per call.

eboekhoudenaccountinginvoicesrelationsconnector
const

EBoekhouden :: section

line 12
'EBoekhouden'
const

EBoekhouden :: api

line 13
'https://api.e-boekhouden.nl/v1'
prop

%EBoekhouden -> sessionToken:string

line 15
void
method

%EBoekhouden -> headers:array

line 17
$this->sessionToken !== void ? ['Authorization: '.$this->sessionToken] : []
static

EBoekhouden :: fields:array

line 19
arr(
	section: 'EBoekhouden',
	config: arr(source: 'Source label shown in the e-Boekhouden audit trail (optional)'),
	secret: arr(api_token: 'API token (Beheer > Instellingen > API)'),
)
method

%EBoekhouden -> session:obj

line 28
Returns the session token, fetching one on first use.
if ($m = $this->missing('api_token')) return $m
if ($this->sessionToken !== void) return static::ok($this->sessionToken)
$res = $this->post('session', ['accessToken' => trim((string)$this->config['api_token']), 'source' => (string)($this->config['source'] ?? 'Phlo')])
if (!$res->ok) return $res
$token = (string)($res->data->token ?? void)
if ($token === void) return static::fail('e-Boekhouden session token missing in response', $res->status)
$this->sessionToken = $token
return static::ok($token)
method

%EBoekhouden -> guard:?obj

line 39
$res = $this->session
return $res->ok ? null : $res
method

%EBoekhouden -> relations (array $query = []):obj

line 44
if ($m = $this->guard) return $m
return $this->get('relation', $query)
method

%EBoekhouden -> createRelation (array $relation):obj

line 49
if ($m = $this->guard) return $m
return $this->post('relation', $relation)
method

%EBoekhouden -> invoices (array $query = []):obj

line 54
if ($m = $this->guard) return $m
return $this->get('invoice', $query)
method

%EBoekhouden -> createInvoice (array $invoice):obj

line 59
if ($m = $this->guard) return $m
return $this->post('invoice', $invoice)
object

%ExactOnline

/phlo/resources/connectors/finance/ExactOnline.phlo

Exact Online connector (OAuth2): read sales invoices and accounts, create sales invoices

The division number sits in the base URL, so a connector speaks to exactly one administration and a second administration needs a second connector with its own config. Exact rotates the refresh token on every refresh, which is why the token store locks; running two apps on one refresh token logs both out.

exactexactonlineaccountinginvoicesoauthconnector
const

ExactOnline :: section

line 12
在ExactOnline资源中定义一个部分,以便进行结构化数据管理。
'ExactOnline'
const

ExactOnline :: tokenUrl

line 13
返回用于从ExactOnline获取OAuth2令牌的URL。
'https://start.exactonline.nl/api/oauth2/token'
method

%ExactOnline -> base:string

line 15
使用配置中的指定部门构建ExactOnline API的基本URL。
'https://start.exactonline.nl/api/v1/'.($this->config['division'] ?? void)
static

ExactOnline :: fields:array

line 17
定义与ExactOnline集成所需的字段,包括OAuth凭据和范围的配置。
arr(
	section: 'ExactOnline',
	config: arr(division: 'Division (administration) number'),
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (managed and rotated after first authorization)',
	),
	scopes: 'OAuth2 authorization code flow; token endpoint refreshes automatically',
)
method

%ExactOnline -> guard:?obj

line 28
检查必需参数 'division'、'client_id'、'client_secret' 和 'refresh_token' 的存在。
$this->missing('division', 'client_id', 'client_secret', 'refresh_token')
method

%ExactOnline -> invoices (array $query = []):obj

line 30
根据提供的查询参数从ExactOnline获取销售发票列表。
if ($m = $this->guard) return $m
return $this->get('salesinvoice/SalesInvoices', $query)
method

%ExactOnline -> accounts (array $query = []):obj

line 35
使用指定的查询参数从ExactOnline检索账户信息。
if ($m = $this->guard) return $m
return $this->get('crm/Accounts', $query)
method

%ExactOnline -> createInvoice (array $invoice):obj

line 40
使用提供的发票数据在ExactOnline中创建新发票。
if ($m = $this->guard) return $m
return $this->post('salesinvoice/SalesInvoices', $invoice)
object

%GoogleCalendar

/phlo/resources/connectors/cloud/GoogleCalendar.phlo

Google Calendar connector (OAuth2): read events and create events

Shares the Google section with Sheets, so one refresh token has to carry both scopes. calendarId defaults to primary, which is the mailbox of the account that authorized, not a shared agenda; give a calendar address for those. Google returns times as RFC3339 with an offset, so keep the timezone rather than casting to a local timestamp.

googlecalendareventsoauthconnector
const

GoogleCalendar :: section

line 12
在Google日历资源中定义一个部分,以便组织事件和细节。
'Google'
const

GoogleCalendar :: tokenUrl

line 13
这是用于从Google Calendar的OAuth 2.0授权服务器获取访问令牌的URL端点。
'https://oauth2.googleapis.com/token'
method

%GoogleCalendar -> base:string

line 15
这定义了访问 Google Calendar API 版本 3 的基础 URL。
'https://www.googleapis.com/calendar/v3'
static

GoogleCalendar :: fields:array

line 17
GoogleCalendar::$fields 提供访问 Google Calendar 的配置详细信息,包括 OAuth 凭据和所需的范围。
arr(
	section: 'Google',
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (scopes: calendar, spreadsheets)',
	),
	scopes: 'https://www.googleapis.com/auth/calendar, https://www.googleapis.com/auth/spreadsheets',
)
method

%GoogleCalendar -> guard:?obj

line 27
检查必需凭据的存在:client_id、client_secret 和 refresh_token。
$this->missing('client_id', 'client_secret', 'refresh_token')
method

%GoogleCalendar -> events (string $calendarId = 'primary', array $query = []):obj

line 29
使用提供的日历ID和可选查询参数从指定的Google日历中检索事件。
if ($m = $this->guard) return $m
return $this->get('calendars/'.rawurlencode($calendarId).'/events', $query)
method

%GoogleCalendar -> createEvent (array $event, string $calendarId = 'primary'):obj

line 34
使用提供的事件详细信息在指定的 Google 日历中创建新事件。
if ($m = $this->guard) return $m
return $this->post('calendars/'.rawurlencode($calendarId).'/events', $event)
object

%GoogleSheets

/phlo/resources/connectors/cloud/GoogleSheets.phlo

Google Sheets connector (OAuth2): read ranges and append rows

Shares the Google section with Calendar, so one refresh token has to carry both scopes. A range is A1 notation including the tab name, e.g. Sheet1!A:D. append() writes with USER_ENTERED, so the sheet parses what you send just as a typist would and a leading zero or a date-like string is reinterpreted; pass RAW when the value has to stay untouched.

googlesheetsspreadsheetoauthconnector
const

GoogleSheets :: section

line 12
定义一个用于在 Google Sheets 资源中组织数据的部分。
'Google'
const

GoogleSheets :: tokenUrl

line 13
这是从 Google Sheets 获取 OAuth 2.0 令牌的 URL 端点。
'https://oauth2.googleapis.com/token'
method

%GoogleSheets -> base:string

line 15
此项表示访问 Google Sheets API 的基本 URL,特别是与电子表格交互的端点。
'https://sheets.googleapis.com/v4/spreadsheets'
static

GoogleSheets :: fields:array

line 17
定义Google Sheets集成的配置字段,包括OAuth凭据和所需的范围。
arr(
	section: 'Google',
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (scopes: calendar, spreadsheets)',
	),
	scopes: 'https://www.googleapis.com/auth/spreadsheets',
)
method

%GoogleSheets -> guard:?obj

line 27
检查必需凭据的存在:client_id、client_secret 和 refresh_token。
$this->missing('client_id', 'client_secret', 'refresh_token')
method

%GoogleSheets -> values ($spreadsheetId, string $range):obj

line 29
从指定的 Google Sheets 电子表格中检索给定电子表格 ID 的范围内的值。
if ($m = $this->guard) return $m
return $this->get($spreadsheetId.'/values/'.rawurlencode($range))
method

%GoogleSheets -> append ($spreadsheetId, string $range, array $rows, string $valueInputOption = 'USER_ENTERED'):obj

line 34
将数据行附加到指定的 Google Sheets 电子表格范围,使用提供的值输入选项。
if ($m = $this->guard) return $m
return $this->post($spreadsheetId.'/values/'.rawurlencode($range).':append?valueInputOption='.$valueInputOption, ['values' => $rows])
object

%Lightspeed

/phlo/resources/connectors/shops/Lightspeed.phlo

Lightspeed Retail (V3) connector: read customers and sales, create customers

Retail V3, so cluster_id names the account and the key and secret authenticate. Lightspeed rate limits per account with a leaky bucket, so raise retries rather than firing calls in a loop. findCustomer() picks its search field from what you pass: an address with an at sign searches on email, anything else on phone.

lightspeedwebshopretailposcustomersconnector
const

Lightspeed :: section

line 12
在Lightspeed应用程序中定义一个部分,允许在该部分内组织和管理内容。
'Lightspeed'
method

%Lightspeed -> base:string

line 14
Lightspeed->base 构建访问 Lightspeed API 的基础 URL,如果可用,则包含配置中的集群 ID。
'https://api.lightspeedapp.com/API/V3/Account/'.($this->config['cluster_id'] ?? void)
method

%Lightspeed -> headers:array

line 16
此方法使用配置中提供的API密钥和密钥检索Lightspeed API身份验证所需的头部。
[static::basic($this->config['api_key'] ?? void, $this->config['api_secret'] ?? void)]
static

Lightspeed :: fields:array

line 18
Lightspeed::$fields 定义了连接到 Lightspeed API 所需的配置字段,包括集群 ID、语言、API 密钥、API 秘密和访问范围。
arr(
	section: 'Lightspeed',
	config: arr(
		cluster_id: 'Account / cluster ID',
		language: 'Language (optional, default nl)',
	),
	secret: arr(
		api_key: 'API key',
		api_secret: 'API secret',
	),
	scopes: 'Customer read/write, Sale read',
)
method

%Lightspeed -> customers (array $query = []):obj

line 31
使用提供的查询参数从Lightspeed API检索客户数据。
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Customer.json', $query)
method

%Lightspeed -> findCustomer ($participant):obj

line 36
根据提供的参与者标识符的格式,通过电子邮件或电话号码查找客户。
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
$field = str_contains((string)$participant, '@') ? 'Email' : 'Phone'
return $this->get('Customer.json', [$field => $participant, 'limit' => 1])
method

%Lightspeed -> customer ($id):obj

line 42
使用指定的客户ID从Lightspeed API检索客户信息。
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Customer/'.$id.'.json')
method

%Lightspeed -> sales (array $query = []):obj

line 47
使用提供的查询参数从API获取销售数据,确保所需的凭据存在。
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Sale.json', $query)
method

%Lightspeed -> createCustomer (array $customer):obj

line 52
通过将提供的客户数据发送到Lightspeed API来创建新客户,确保所需参数存在。
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->post('Customer.json', $customer)
object

%MessageBird

/phlo/resources/connectors/chat/MessageBird.phlo

MessageBird connector: send SMS

Needs access_key and an originator, the sender shown to the recipient; some countries refuse an alphanumeric originator, so a real number is the safer choice. Pass an array as the recipient to send one message to several numbers at once.

messagebirdsmsmessagingconnector
const

MessageBird :: section

line 12
在Phlo应用程序中定义MessageBird的一个部分。
'MessageBird'
method

%MessageBird -> base:string

line 14
这定义了MessageBird API的基础URL,用于发送和接收消息。
'https://rest.messagebird.com'
method

%MessageBird -> headers:array

line 16
这获取了使用提供的访问密钥对MessageBird API请求进行身份验证所需的头部信息。
['Authorization: AccessKey '.($this->config['access_key'] ?? void)]
static

MessageBird :: fields:array

line 18
定义了MessageBird配置的字段,包括发件人和访问密钥。
arr(
	section: 'MessageBird',
	config: arr(originator: 'Originator (sender number or name)'),
	secret: arr(access_key: 'Access key'),
)
static

MessageBird :: errorMessage ($data, string $raw, int $status):string

line 24
从MessageBird API响应中检索错误消息(如果可用);否则,回退到父类的错误消息方法。
if (is_object($data) && isset($data->errors[0]->description)) return (string)$data->errors[0]->description
return parent::errorMessage($data, $raw, $status)
method

%MessageBird -> sms ($to, $body, array $extra = []):obj

line 29
使用MessageBird API向指定的接收者发送SMS消息,需提供访问密钥和发件人配置。
if ($m = $this->missing('access_key', 'originator')) return $m
$recipients = is_array($to) ? $to : [$to]
return $this->post('messages', ['originator' => $this->config['originator'], 'recipients' => $recipients, 'body' => $body] + $extra)
object

%MicrosoftGraph

/phlo/resources/connectors/cloud/MicrosoftGraph.phlo

Microsoft Graph connector (app-only client credentials): read users and calendars, send mail, create events

This is the app-only flow: the token belongs to the registration, not to a person, so it needs admin-consented application permissions and every call names the mailbox it acts on. Tokens are cached in APCu for their lifetime, so a run without APCu fetches one per request. Set mailbox to spare yourself passing a user each time.

microsoftgraphoffice365calendarmailconnector
const

MicrosoftGraph :: section

line 12
此功能与 Microsoft Graph API 交互,以管理资源中的部分。
'Microsoft'
method

%MicrosoftGraph -> base:string

line 14
这定义了访问 Microsoft Graph API 的基本 URL,允许与 Microsoft 服务和资源进行交互。
'https://graph.microsoft.com/v1.0'
method

%MicrosoftGraph -> headers:array

line 16
此项使用承载令牌检索与 Microsoft Graph 进行身份验证所需的头部信息。
[static::bearer((string)$this->token)]
static

MicrosoftGraph :: fields:array

line 18
定义连接到 Microsoft Graph 所需的配置字段,包括租户 ID、客户端 ID、邮箱、客户端密钥和应用程序权限。
arr(
	section: 'Microsoft',
	config: arr(
		tenant_id: 'Azure AD tenant ID',
		client_id: 'App registration client ID',
		mailbox: 'Default mailbox / user UPN for calendar and mail (optional)',
	),
	secret: arr(client_secret: 'App registration client secret'),
	scopes: 'Application permissions: User.Read.All, Calendars.ReadWrite, Mail.Send',
)
prop

%MicrosoftGraph -> token:string

line 29
使用fetchToken方法从Microsoft Graph获取访问令牌。
$this->fetchToken()
method

%MicrosoftGraph -> fetchToken:string

line 35
Caches the app-only token in APCu until a minute before it expires.
$tenant = $this->config['tenant_id'] ?? void
$id = $this->config['client_id'] ?? void
$secret = $this->config['client_secret'] ?? void
if ($tenant === void || $id === void || $secret === void) return void
$key = 'phlo:graph:'.$tenant.colon.$id
if (function_exists('apcu_fetch')){
	$cached = apcu_fetch($key)
	if ($cached) return $cached
}
$res = $this->dispatch(static::build('POST', 'https://login.microsoftonline.com/'.$tenant.'/oauth2/v2.0/token', null, [], null, ['grant_type' => 'client_credentials', 'client_id' => $id, 'client_secret' => $secret, 'scope' => 'https://graph.microsoft.com/.default']))
if (!$res->ok) return void
$token = $res->data->access_token ?? void
$expires = (int)($res->data->expires_in ?? 3600)
if ($token !== void && function_exists('apcu_store')) apcu_store($key, $token, max(60, $expires - 60))
return $token
method

%MicrosoftGraph -> mailbox ($user = null):string

line 53
检索与指定用户关联的邮箱,如果未提供用户,则默认为配置的邮箱。
$user ?? ($this->config['mailbox'] ?? void)
method

%MicrosoftGraph -> users (array $query = []):obj

line 55
根据提供的查询参数从 Microsoft Graph 检索用户列表。
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
return $this->get('users', $query)
method

%MicrosoftGraph -> user ($id):obj

line 60
使用指定的用户ID从Microsoft Graph获取用户信息。
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
return $this->get('users/'.rawurlencode((string)$id))
method

%MicrosoftGraph -> events ($user = null, array $query = []):obj

line 65
使用 Microsoft Graph API 从指定用户的 Microsoft 邮箱中获取事件。
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->get('users/'.rawurlencode((string)$mailbox).'/events', $query)
method

%MicrosoftGraph -> sendMail ($message, $user = null, bool $save = true):obj

line 72
使用 Microsoft Graph API 发送电子邮件消息,可以选择指定用户以及是否将消息保存到已发送邮件文件夹。
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->post('users/'.rawurlencode((string)$mailbox).'/sendMail', ['message' => $message, 'saveToSentItems' => $save])
method

%MicrosoftGraph -> createEvent (array $event, $user = null):obj

line 79
使用提供的事件详细信息在指定用户的 Microsoft 日历中创建新事件。
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->post('users/'.rawurlencode((string)$mailbox).'/events', $event)
object

%Moneybird

/phlo/resources/connectors/finance/Moneybird.phlo

Moneybird connector: read contacts and invoices, create sales invoices

Needs an administration_id and a personal access token with rights for contacts and invoices. findContact() searches on one match, so use it to look up rather than to list. An invoice is created as a draft: sending or booking it is a separate step in Moneybird.

moneybirdaccountinginvoicescontactsconnector
const

Moneybird :: section

line 12
在Moneybird集成中定义一个部分,用于组织相关功能。
'Moneybird'
method

%Moneybird -> base:string

line 14
这会检索Moneybird API的基础URL,如果可用,则包含配置中的管理ID。
'https://moneybird.com/api/v2/'.($this->config['administration_id'] ?? void)
method

%Moneybird -> headers:array

line 16
返回与Moneybird API身份验证所需的headers,使用从配置中的访问令牌派生的bearer token。
[static::bearer($this->config['access_token'] ?? void)]
static

Moneybird :: fields:array

line 18
定义了Moneybird集成的字段,包括管理ID和用于访问联系人和发票的个人访问令牌。
arr(
	section: 'Moneybird',
	config: arr(administration_id: 'Administration ID'),
	secret: arr(access_token: 'Personal access token with read/write for contacts and invoices'),
)
method

%Moneybird -> contacts (array $query = []):obj

line 24
根据提供的查询参数从Moneybird获取联系人列表。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts.json', $query)
method

%Moneybird -> findContact ($query):obj

line 29
根据提供的查询在Moneybird中查找联系人,返回找到的第一个结果。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts.json', ['query' => $query, 'per_page' => 1])
method

%Moneybird -> contact ($id):obj

line 34
从Moneybird检索与指定ID关联的联系人信息,需提供管理ID和访问令牌。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts/'.$id.'.json')
method

%Moneybird -> invoices (array $query = []):obj

line 39
根据提供的查询参数从Moneybird获取发票,要求提供'administration_id'和'access_token'。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('sales_invoices.json', $query)
method

%Moneybird -> createContact (array $contact):obj

line 44
使用提供的联系信息在Moneybird中创建新联系人。它需要一个管理ID和访问令牌才能正常工作。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->post('contacts.json', ['contact' => $contact])
method

%Moneybird -> createInvoice (array $invoice):obj

line 49
使用提供的发票数据在Moneybird中创建新发票。
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->post('sales_invoices.json', ['sales_invoice' => $invoice])
object

%OAuthConnector

/phlo/resources/connectors/OAuthConnector.phlo

Base class for OAuth2 connectors: stored, auto-refreshed bearer access tokens via TokenStore, on the OAuth2 primitive

Use this instead of Connector when the provider hands out access tokens that expire. Give it client_id, client_secret and a refresh_token and TokenStore keeps the access token and renews it in time; a subclass only names its section and tokenUrl.

oauthoauth2connectorbasetokenrefresh
const

OAuthConnector :: tokenUrl

line 12
指定获取OAuth令牌的URL。
void
method

%OAuthConnector -> oauthKey:string

line 14
获取OAuthConnector中当前部分的OAuth密钥。
static::section
prop

%OAuthConnector -> token:?string

line 16
使用提供的凭据和刷新令牌从 TokenStore 中检索 OAuth 令牌。
TokenStore::access($this->oauthKey, static::tokenUrl, $this->config['client_id'] ?? void, $this->config['client_secret'] ?? void, ['refresh_token' => $this->config['refresh_token'] ?? null])
method

%OAuthConnector -> headers:array

line 18
返回OAuth认证所需的头部,包括从实例的token派生的bearer token。
[static::bearer((string)$this->token)]
method

%OAuthConnector -> authed:bool

line 20
通过验证OAuth令牌是否非空来检查其是否已认证。
(string)$this->token !== void
object

%Resend

/phlo/resources/connectors/chat/Resend.phlo

Resend connector: send transactional email via the HTTP API

Needs api_key and a from_email on a domain you verified with Resend, otherwise the send is refused. send() takes HTML; leave it out for a plain text mail and add anything else Resend accepts through extra.

resendemailtransactionalmessagingconnector
const

Resend :: section

line 12
Resend::section 允许您在 Resend 资源中定义一个部分,以组织相关内容。
'Resend'
method

%Resend -> base:string

line 14
设置 Resend API 的基础 URL。
'https://api.resend.com'
method

%Resend -> headers:array

line 16
使用配置中提供的API密钥设置Resend请求的头部。
[static::bearer($this->config['api_key'] ?? void)]
static

Resend :: fields:array

line 18
定义Resend配置的字段,包括默认发件人电子邮件和API密钥。
arr(
	section: 'Resend',
	config: arr(from_email: 'Default sender, e.g. "App <noreply@yourdomain.com>"'),
	secret: arr(api_key: 'API key (re_...)'),
)
method

%Resend -> send ($to, $subject, $html = void, array $extra = []):obj

line 24
使用配置的发件人电子邮件地址向指定的收件人发送电子邮件,主题和可选的 HTML 内容。
if ($m = $this->missing('api_key', 'from_email')) return $m
$email = ['from' => $this->config['from_email'], 'to' => is_array($to) ? $to : [$to], 'subject' => $subject]
if ($html !== void) $email['html'] = $html
return $this->post('emails', $email + $extra)
object

%Shopify

/phlo/resources/connectors/shops/Shopify.phlo

Shopify Admin API connector: read customers, orders and products; create draft orders and products; update inventory

Needs shop_domain including myshopify.com and an admin access token; api_version defaults to 2024-01 and a Shopify version is supported for a limited time, so set the one you tested against rather than leaning on the default. setInventory() writes an absolute level, not a difference, so read the current one first when you mean to add. A draft order is not an order until it is completed in Shopify.

shopifywebshopecommerceordersproductsconnector
const

Shopify :: section

line 12
在Shopify主题中定义一个部分,允许在商店内自定义内容和布局。
'Shopify'
method

%Shopify -> base:string

line 14
使用配置中的商店域名和API版本构建Shopify API的基础URL。
'https://'.($this->config['shop_domain'] ?? void).'/admin/api/'.($this->config['api_version'] ?? '2024-01')
method

%Shopify -> headers:array

line 16
这获取了Shopify API身份验证所需的头信息,包括来自配置的访问令牌。
['X-Shopify-Access-Token: '.($this->config['access_token'] ?? void)]
static

Shopify :: fields:array

line 18
定义与Shopify集成的配置字段,包括商店域名、API版本、访问令牌和所需的权限范围。
arr(
	section: 'Shopify',
	config: arr(
		shop_domain: 'Shop domain, e.g. your-store.myshopify.com',
		api_version: 'Admin API version (optional, default 2024-01)',
	),
	secret: arr(access_token: 'Admin API access token (shpat_...)'),
	scopes: 'read_customers, read_orders, read_products, write_draft_orders, write_inventory',
)
method

%Shopify -> customers (array $query = []):obj

line 28
使用提供的查询参数从 Shopify 获取客户列表。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers.json', $query)
method

%Shopify -> searchCustomers ($query, int $limit = 10):obj

line 33
根据查询字符串和可选的结果数量限制在Shopify中搜索客户。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers/search.json', ['query' => $query, 'limit' => $limit])
method

%Shopify -> customer ($id):obj

line 38
使用指定的客户ID从Shopify获取客户信息。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers/'.$id.'.json')
method

%Shopify -> orders (array $query = []):obj

line 43
根据提供的查询参数从Shopify获取订单列表。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('orders.json', $query)
method

%Shopify -> products (array $query = []):obj

line 48
使用提供的查询参数从Shopify获取产品列表。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('products.json', $query)
method

%Shopify -> createDraftOrder (array $order):obj

line 53
使用提供的订单详细信息在Shopify中创建草稿订单。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('draft_orders.json', ['draft_order' => $order])
method

%Shopify -> createProduct (array $product):obj

line 58
使用提供的产品详细信息在 Shopify 中创建新产品。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('products.json', ['product' => $product])
method

%Shopify -> setInventory ($inventoryItemId, $locationId, int $available):obj

line 63
为Shopify中特定库存项目在给定位置设置可用库存水平。
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('inventory_levels/set.json', ['inventory_item_id' => $inventoryItemId, 'location_id' => $locationId, 'available' => $available])
object

%Slack

/phlo/resources/connectors/chat/Slack.phlo

Slack connector: post messages, read channel history and list channels

Needs a bot token, and the bot has to be invited into the channel it posts in, else the call comes back ok false with not_in_channel. Slack answers HTTP 200 even when it refuses, so read the outcome from the result, never from the status.

slackmessagingchatconnector
const

Slack :: section

line 12
在Slack消息中创建一个部分块,允许包含文本和其他元素。
'Slack'
const

Slack :: api

line 13
此项提供对Slack API的访问,允许与Slack服务和功能集成。
'https://slack.com/api'
method

%Slack -> headers:array

line 15
返回使用 bearer token 向 Slack API 发出请求时所需的身份验证头。
[static::bearer($this->config['bot_token'] ?? void)]
static

Slack :: fields:array

line 17
定义与Slack集成的配置字段,包括机器人令牌、签名密钥和所需的作用域。
arr(
	section: 'Slack',
	secret: arr(
		bot_token: 'Bot user OAuth token (xoxb-...)',
		signing_secret: 'Signing secret for inbound webhook verification (optional)',
	),
	scopes: 'chat:write, channels:history, channels:read',
)
method

%Slack -> result (obj $res):obj

line 26
处理来自Slack API调用的结果,如果成功则返回响应,如果发生错误则返回错误消息。
if (!$res->ok) return $res
$data = $res->data
if (is_object($data) && ($data->ok ?? null) === false) return static::fail($data->error ?? 'Slack API error', $res->status)
return $res
method

%Slack -> send ($channel, $text, array $extra = []):obj

line 33
使用Slack API向指定的Slack频道发送消息。它需要配置的bot令牌,并通过数组接受额外参数。
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->post('chat.postMessage', ['channel' => $channel, 'text' => $text] + $extra))
method

%Slack -> history ($channel, int $limit = 20):obj

line 38
从指定的Slack频道获取消息历史记录,并可选择限制返回的消息数量。
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->get('conversations.history', ['channel' => $channel, 'limit' => $limit]))
method

%Slack -> channels (int $limit = 100, string $types = 'public_channel'):obj

line 43
从Slack获取频道列表,可以选择限制频道数量并指定要返回的频道类型。
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->get('conversations.list', ['limit' => $limit, 'types' => $types]))
object

%Telegram

/phlo/resources/connectors/chat/Telegram.phlo

Telegram Bot API connector: send messages, photos and documents; poll updates

Needs a bot token from BotFather and a chat_id, and a person has to have written to the bot before it can write to them. Like Slack, the API answers 200 on refusal, which the connector already translates into ok false. photo() and document() take a URL or an existing file_id, not raw bytes.

telegrambotmessagingchatconnector
const

Telegram :: section

line 12
在Telegram资源中定义一个部分,允许对相关内容进行有组织的分组。
'Telegram'
method

%Telegram -> base:string

line 14
使用配置中提供的机器人令牌构建Telegram Bot API的基本URL。
'https://api.telegram.org/bot'.($this->config['bot_token'] ?? void)
static

Telegram :: fields:array

line 16
定义与Telegram配置相关的字段,包括机器人令牌和用于验证的可选Webhook密钥。
arr(
	section: 'Telegram',
	secret: arr(
		bot_token: 'Bot token from BotFather',
		webhook_secret: 'Webhook secret token for inbound verification (optional)',
	),
	help: 'Create a bot via BotFather and store its token. The chat_id is the recipient or chat to message.',
)
method

%Telegram -> result (obj $res):obj

line 25
处理来自Telegram API响应的结果,如果成功则返回响应,若检测到错误则返回失败消息。
if (!$res->ok) return $res
$data = $res->data
if (is_object($data) && ($data->ok ?? null) === false) return static::fail($data->description ?? 'Telegram API error', $res->status)
return $res
method

%Telegram -> send ($chatId, $text, array $extra = []):obj

line 32
使用提供的聊天ID和文本向指定的Telegram聊天发送消息,并可以选择额外参数。
if ($m = $this->missing('bot_token')) return $m
return $this->result($this->post('sendMessage', ['chat_id' => $chatId, 'text' => $text] + $extra))
method

%Telegram -> photo ($chatId, $photo, $caption = void, array $extra = []):obj

line 37
向指定的Telegram聊天发送照片,附带可选的标题和额外参数。
if ($m = $this->missing('bot_token')) return $m
$payload = ['chat_id' => $chatId, 'photo' => $photo]
if ($caption !== void) $payload['caption'] = $caption
return $this->result($this->post('sendPhoto', $payload + $extra))
method

%Telegram -> document ($chatId, $document, $caption = void, array $extra = []):obj

line 44
向指定的Telegram聊天发送文档,可选地包括标题和其他参数。
if ($m = $this->missing('bot_token')) return $m
$payload = ['chat_id' => $chatId, 'document' => $document]
if ($caption !== void) $payload['caption'] = $caption
return $this->result($this->post('sendDocument', $payload + $extra))
method

%Telegram -> updates (int $offset = 0, int $limit = 100):obj

line 51
从Telegram bot API获取更新,允许通过'offset'和'limit'参数进行分页。
if ($m = $this->missing('bot_token')) return $m
return $this->result($this->get('getUpdates', ['offset' => $offset, 'limit' => $limit]))
object

%TokenStore

/phlo/resources/connectors/TokenStore.phlo

Persisted OAuth2 token store with automatic refresh via the OAuth2 resource

Tokens are kept as one file per key under data/tokens with 0600 rights, so a refresh survives a restart and every worker shares it. A refresh takes an exclusive lock, which matters with providers that rotate the refresh token: without it the losing request is left holding a dead one.

oauthoauth2tokenrefreshstorecredentials
static

TokenStore :: path ($key):string

line 11
TokenStore::$path 属性定义了存储令牌数据的文件路径,该路径基于提供的键的经过清理的版本格式化为 JSON 文件。
data.'tokens/'.preg_replace('/[^a-z0-9_.-]+/i', us, (string)$key).'.json'
static

TokenStore :: read ($key):array

line 13
通过键从TokenStore读取令牌,如果文件存在且可访问,则返回令牌数组。
$file = static::path($key)
if (!is_file($file)) return []
@chmod(data.'tokens', 0700)
@chmod($file, 0600)
return (array)json_read($file, true)
static

TokenStore :: write ($key, array $token):void

line 21
该方法将令牌写入指定文件中的安全目录,确保目录和文件具有适当的权限。
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
@chmod($dir, 0700)
$file = static::path($key)
json_write($file, $token)
@chmod($file, 0600)
static

TokenStore :: valid (array $token):bool

line 30
通过确保访问令牌存在且未过期(允许在过期前有30秒的缓冲)来检查访问令牌是否有效。
($token['access_token'] ?? void) !== void && (int)($token['expires_at'] ?? 0) > time() + 30
static

TokenStore :: store ($res, $refresh):array

line 32
存储访问令牌、刷新令牌和过期时间以用于身份验证。
$token = [
	'access_token' => $res['access_token'],
	'refresh_token' => $res['refresh_token'] ?? $refresh,
	'expires_at' => time() + (int)($res['expires_in'] ?? 3600),
]
return $token
static

TokenStore :: lock ($key)

line 44
Takes an exclusive lock around one key's refresh cycle.
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
$lock = @fopen(static::path($key).'.lock', 'c')
if (!$lock || !flock($lock, LOCK_EX)){
	$lock && fclose($lock)
	return null
}
return $lock
static

TokenStore :: access ($key, $tokenUrl, $clientId, $clientSecret, array $seed = []):?string

line 55
从 TokenStore 中检索访问令牌,如有必要,使用刷新令牌进行刷新。
$token = static::read($key)
if (static::valid($token)) return $token['access_token']
$lock = static::lock($key)
if (!$lock) return null
try {
	$token = static::read($key)
	if (!($token['refresh_token'] ?? null) && ($seed['refresh_token'] ?? null)){
		$token = ['refresh_token' => $seed['refresh_token']]
		static::write($key, $token)
	}
	if (static::valid($token)) return $token['access_token']
	$refresh = $token['refresh_token'] ?? null
	if (!$refresh || !$tokenUrl || !$clientId) return null
	$res = OAuth2::refresh($tokenUrl, $clientId, $clientSecret, $refresh)
	if (!($res['access_token'] ?? null)) return null
	$token = static::store($res, $refresh)
	static::write($key, $token)
	return $token['access_token']
} finally {
	flock($lock, LOCK_UN)
	fclose($lock)
}
object

%Twilio

/phlo/resources/connectors/chat/Twilio.phlo

Twilio connector: send SMS and read message status

Needs account_sid and auth_token, plus either a from_number in E.164 or a messaging_service_sid; without one of the two the send is refused before it leaves. Twilio speaks form encoding rather than JSON, which sms() handles, so pass extra fields in Twilio's own capitalized names. A returned sid says it was accepted, not delivered; message() tells you what became of it.

twiliosmsmessagingconnector
const

Twilio :: section

line 12
在Phlo应用程序中定义Twilio集成的部分。
'Twilio'
method

%Twilio -> base:string

line 14
构建用于访问 Twilio API 的基础 URL,使用配置中的账户 SID。
'https://api.twilio.com/2010-04-01/Accounts/'.($this->config['account_sid'] ?? void)
method

%Twilio -> headers:array

line 16
使用配置中提供的账户 SID 和身份验证令牌检索 Twilio API 请求所需的头部信息。
[static::basic($this->config['account_sid'] ?? void, $this->config['auth_token'] ?? void)]
static

Twilio :: fields:array

line 18
定义用于配置 Twilio 集成的字段,包括账户 SID、发送者号码和可选的消息服务 SID,以及身份验证令牌。
arr(
	section: 'Twilio',
	config: arr(
		account_sid: 'Account SID (ACxxxx)',
		from_number: 'Sender number in E.164, e.g. +31600000000 (use this or messaging_service_sid)',
		messaging_service_sid: 'Messaging Service SID (optional alternative to from_number)',
	),
	secret: arr(auth_token: 'Auth token'),
)
method

%Twilio -> sms ($to, $body, array $extra = []):obj

line 28
使用Twilio的API发送SMS消息,配置中需要提供from_number或messaging_service_sid。
if ($m = $this->missing('account_sid', 'auth_token')) return $m
$from = $this->config['from_number'] ?? void
$service = $this->config['messaging_service_sid'] ?? void
if ($from === void && $service === void) return static::fail('Twilio from_number or messaging_service_sid required')
$fields = ['To' => $to, 'Body' => $body] + $extra
if ($service !== void) $fields['MessagingServiceSid'] = $service
else $fields['From'] = $from
return $this->form('Messages.json', $fields)
method

%Twilio -> message ($sid):obj

line 39
使用指定的消息 SID 从 Twilio 检索消息。
if ($m = $this->missing('account_sid', 'auth_token')) return $m
return $this->get('Messages/'.$sid.'.json')

最近更新于 2026年8月7日

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。