Skip to content

Commit 6f47a71

Browse files
committed
feat: airtable addon v2
1 parent b34aa48 commit 6f47a71

File tree

5 files changed

+256
-183
lines changed

5 files changed

+256
-183
lines changed

forms-bridge/addons/airtable/class-airtable-addon.php

Lines changed: 101 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
<?php
2+
/**
3+
* Class Airtable_Addon
4+
*
5+
* @package formsbridge
6+
*/
27

38
namespace FORMS_BRIDGE;
49

10+
use FBAPI;
11+
use WP_Error;
12+
513
if ( ! defined( 'ABSPATH' ) ) {
614
exit();
715
}
816

917
require_once 'class-airtable-form-bridge.php';
18+
require_once 'hooks.php';
1019

1120
/**
1221
* Airtable addon class.
@@ -46,34 +55,13 @@ public function ping( $backend ) {
4655
array(
4756
'name' => '__airtable-' . time(),
4857
'backend' => $backend,
49-
'endpoint' => '/',
58+
'endpoint' => '/v0/meta/bases',
5059
'method' => 'GET',
5160
)
5261
);
5362

54-
$backend = $bridge->backend;
55-
if ( ! $backend ) {
56-
Logger::log( 'Airtable backend ping error: Bridge has no valid backend', Logger::ERROR );
57-
return false;
58-
}
59-
60-
$credential = $backend->credential;
61-
if ( ! $credential ) {
62-
Logger::log( 'Airtable backend ping error: Backend has no valid credential', Logger::ERROR );
63-
return false;
64-
}
65-
66-
$parsed = wp_parse_url( $backend->base_url );
67-
$host = $parsed['host'] ?? '';
68-
69-
if ( 'api.airtable.com' !== $host ) {
70-
Logger::log( 'Airtable backend ping error: Backend does not point to the Airtable API endpoints', Logger::ERROR );
71-
return false;
72-
}
73-
74-
$access_token = $credential->get_access_token();
75-
76-
if ( ! $access_token ) {
63+
$response = $bridge->submit();
64+
if ( is_wp_error( $response ) ) {
7765
Logger::log( 'Airtable backend ping error: Unable to recover the credential access token', Logger::ERROR );
7866
return false;
7967
}
@@ -90,32 +78,24 @@ public function ping( $backend ) {
9078
* @return array|WP_Error
9179
*/
9280
public function fetch( $endpoint, $backend ) {
93-
$backend = FBAPI::get_backend( $backend );
94-
if ( ! $backend ) {
95-
return new WP_Error( 'invalid_backend' );
96-
}
97-
98-
$credential = $backend->credential;
99-
if ( ! $credential ) {
100-
return new WP_Error( 'invalid_credential' );
101-
}
81+
$endpoints = $this->get_endpoints( $backend );
10282

103-
$access_token = $credential->get_access_token();
104-
if ( ! $access_token ) {
105-
return new WP_Error( 'invalid_credential' );
83+
if ( is_wp_error( $endpoint ) ) {
84+
return $endpoint;
10685
}
10786

108-
$response = http_bridge_get(
109-
'https://api.airtable.com/v0/meta/bases',
110-
array(),
111-
array(
112-
'Authorization' => "Bearer {$access_token}",
113-
'Accept' => 'application/json',
114-
)
87+
$response = array(
88+
'data' => array( 'tables' => array() ),
11589
);
11690

117-
if ( is_wp_error( $response ) ) {
118-
return $response;
91+
foreach ( $endpoints as $endpoint ) {
92+
list( $base_id, $table_name ) = array_slice( explode( '/', $endpoint ), 2 );
93+
94+
$response['data']['tables'][] = array(
95+
'endpoint' => $endpoint,
96+
'name' => $table_name,
97+
'base' => $base_id,
98+
);
11999
}
120100

121101
return $response;
@@ -130,18 +110,36 @@ public function fetch( $endpoint, $backend ) {
130110
* @return array|WP_Error
131111
*/
132112
public function get_endpoints( $backend, $method = null ) {
133-
$response = $this->fetch( null, $backend );
113+
$bridge = new Airtable_Form_Bridge(
114+
array(
115+
'name' => '__airtable-endpoints',
116+
'backend' => $backend,
117+
'endpoint' => '/v0/meta/bases',
118+
'method' => 'GET',
119+
),
120+
);
121+
122+
$response = $bridge->submit();
134123

135124
if ( is_wp_error( $response ) || empty( $response['data']['bases'] ) ) {
136125
return array();
137126
}
138127

139-
return array_map(
140-
function ( $base ) {
141-
return '/v0/' . $base['id'] . '/' . $base['tables'][0]['id'];
142-
},
143-
$response['data']['bases']
144-
);
128+
$endpoints = array();
129+
foreach ( $response['data']['bases'] as $base ) {
130+
$response = $bridge->patch( array( 'endpoint' => "/v0/meta/bases/{$base['id']}/tables" ) )
131+
->submit();
132+
133+
if ( is_wp_error( $response ) ) {
134+
break;
135+
}
136+
137+
foreach ( $response['data']['tables'] as $table ) {
138+
$endpoints[] = "/v0/{$base['id']}/{$table['name']}";
139+
}
140+
}
141+
142+
return $endpoints;
145143
}
146144

147145
/**
@@ -159,30 +157,14 @@ public function get_endpoint_schema( $endpoint, $backend, $method = null ) {
159157
return array();
160158
}
161159

162-
$bridge = null;
163-
$bridges = FBAPI::get_addon_bridges( self::NAME );
164-
foreach ( $bridges as $candidate ) {
165-
$data = $candidate->data();
166-
if ( ! $data ) {
167-
continue;
168-
}
169-
170-
if (
171-
$data['endpoint'] === $endpoint &&
172-
$data['backend'] === $backend
173-
) {
174-
/**
175-
* Current bridge.
176-
*
177-
* @var Airtable_Form_Bridge
178-
*/
179-
$bridge = $candidate;
180-
}
181-
}
182-
183-
if ( ! isset( $bridge ) ) {
184-
return array();
185-
}
160+
$bridge = new Airtable_Form_Bridge(
161+
array(
162+
'name' => '__airtable-endpoint-schema',
163+
'method' => 'GET',
164+
'backend' => $backend,
165+
'endpoint' => $endpoint,
166+
)
167+
);
186168

187169
$fields = $bridge->get_fields();
188170

@@ -192,9 +174,51 @@ public function get_endpoint_schema( $endpoint, $backend, $method = null ) {
192174

193175
$schema = array();
194176
foreach ( $fields as $field ) {
177+
if (
178+
in_array(
179+
$field['type'],
180+
array(
181+
'aiText',
182+
'formula',
183+
'autoNumber',
184+
'button',
185+
'count',
186+
'createdBy',
187+
'createdTime',
188+
'lastModifiedBy',
189+
'lastModifiedTime',
190+
'rollup',
191+
'externalSyncSource',
192+
'multipleAttachments',
193+
),
194+
true,
195+
)
196+
) {
197+
continue;
198+
}
199+
200+
switch ( $field['type'] ) {
201+
case 'rating':
202+
case 'number':
203+
$type = 'number';
204+
break;
205+
case 'checkbox':
206+
$type = 'boolean';
207+
break;
208+
case 'multipleSelects':
209+
case 'multipleCollaborators':
210+
case 'multipleLookupValues':
211+
case 'multipleRecordLinks':
212+
$type = 'array';
213+
break;
214+
default:
215+
$type = 'string';
216+
break;
217+
}
218+
195219
$schema[] = array(
196220
'name' => $field['name'],
197-
'schema' => array( 'type' => $field['type'] ),
221+
'schema' => array( 'type' => $type ),
198222
);
199223
}
200224

forms-bridge/addons/airtable/class-airtable-form-bridge.php

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,74 @@ public function __construct( $data ) {
2828
}
2929

3030
/**
31-
* Fetches the fields of the Airtable table and returns them as an array.
31+
* Gets the base id from the bridge endpoint.
32+
*
33+
* @return string|null
34+
*/
35+
private function base_id() {
36+
preg_match( '/\/v\d+\/([^\/]+)\/([^\/]+)/', $this->endpoint, $matches );
37+
38+
if ( 3 !== count( $matches ) ) {
39+
return null;
40+
}
41+
42+
return $matches[1];
43+
}
44+
45+
/**
46+
* Gets the table id from the bridge endpoint.
3247
*
33-
* @param Backend|null $backend Bridge backend instance.
48+
* @return string|null
49+
*/
50+
private function table_id() {
51+
preg_match( '/\/v\d+\/([^\/]+)\/([^\/]+)/', $this->endpoint, $matches );
52+
53+
if ( 3 !== count( $matches ) ) {
54+
return null;
55+
}
56+
57+
return explode( '/', $matches[2] )[0];
58+
}
59+
60+
/**
61+
* Fetches the fields of the Airtable table and returns them as an array.
3462
*
3563
* @return array<mixed>|WP_Error
3664
*/
37-
public function get_fields( $backend = null ) {
65+
public function get_fields() {
3866
if ( ! $this->is_valid ) {
39-
return new WP_Error( 'invalid_bridge' );
67+
return new WP_Error( 'invalid_bridge', 'The bridge is invalid', $this->data );
4068
}
4169

42-
if ( ! $backend ) {
43-
$backend = $this->backend;
70+
$base_id = $this->base_id();
71+
$table_id = $this->table_id();
72+
73+
if ( ! $base_id || ! $table_id ) {
74+
return new WP_Error( 'invalid_endpoint', 'The bridge has an invalid endpoint', $this->data );
4475
}
4576

46-
$response = $backend->get( $this->endpoint . '?maxRecords=1' );
77+
$response = $this->patch(
78+
array(
79+
'method' => 'GET',
80+
'endpoint' => "/v0/meta/bases/{$base_id}/tables",
81+
)
82+
)->submit();
4783

4884
if ( is_wp_error( $response ) ) {
4985
return $response;
5086
}
5187

52-
if ( empty( $response['data']['fields'] ) ) {
53-
return array();
88+
foreach ( $response['data']['tables'] as $candidate ) {
89+
if ( $table_id === $candidate['id'] || $table_id === $candidate['name'] ) {
90+
$table = $candidate;
91+
}
92+
}
93+
94+
if ( ! isset( $table ) ) {
95+
return new WP_Error( 'not_found', 'Table not found', $this->data );
5496
}
5597

56-
return $response['data']['fields'];
98+
return $table['fields'];
5799
}
58100

59101
/**
@@ -78,27 +120,27 @@ public function submit( $payload = array(), $attachments = array() ) {
78120
return new WP_Error( 'invalid_backend', 'Backend not found' );
79121
}
80122

81-
$fields = $this->get_fields( $backend );
82-
if ( is_wp_error( $fields ) ) {
83-
return $fields;
84-
}
85-
86-
$payload = self::flatten_payload( $payload );
87-
88-
$records = array();
89-
foreach ( $fields as $field ) {
90-
$field_name = $field['name'];
91-
if ( isset( $payload[ $field_name ] ) ) {
92-
$records['fields'][ $field_name ] = $payload[ $field_name ];
93-
}
94-
}
95-
96123
$endpoint = $this->endpoint;
97124
$method = $this->method;
98125

99126
if ( 'POST' === $method ) {
127+
$fields = $this->get_fields( $backend );
128+
if ( is_wp_error( $fields ) ) {
129+
return $fields;
130+
}
131+
132+
$payload = self::flatten_payload( $payload );
133+
134+
$record = array();
135+
foreach ( $fields as $field ) {
136+
$field_name = $field['name'];
137+
if ( isset( $payload[ $field_name ] ) ) {
138+
$record['fields'][ $field_name ] = $payload[ $field_name ];
139+
}
140+
}
141+
100142
$payload = array(
101-
'records' => array( $records ),
143+
'records' => array( $record ),
102144
);
103145
}
104146

0 commit comments

Comments
 (0)