├── AirtablePlugin.php ├── LICENSE.txt ├── README.md ├── composer.json ├── controllers └── Airtable_RecordsController.php ├── models └── Airtable_RecordModel.php ├── releases.json ├── resources ├── css │ ├── Airtable_Style.css │ └── widgets │ │ └── Airtable_AirtableWidget.css ├── icon-mask.svg ├── icon.svg ├── images │ └── plugin.png ├── js │ ├── Airtable_Script.js │ └── widgets │ │ └── Airtable_AirtableWidget.js └── screenshots │ └── plugin_logo.png ├── services ├── Airtable_BaseService.php └── Airtable_RecordService.php ├── templates ├── Airtable_Settings.twig ├── index.html └── widgets │ ├── Airtable_AirtableWidget_Body.twig │ └── Airtable_AirtableWidget_Settings.twig ├── translations └── en.php ├── variables └── AirtableVariable.php ├── vendor ├── autoload.php └── composer │ ├── ClassLoader.php │ ├── LICENSE │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ └── autoload_static.php └── widgets └── Airtable_AirtableWidget.php /AirtablePlugin.php: -------------------------------------------------------------------------------- 1 | on('entries.saveEntry', function(Event $event) { 32 | * // ... 33 | * }); 34 | * 35 | * or loading any third party Composer packages via: 36 | * 37 | * require_once __DIR__ . '/vendor/autoload.php'; 38 | * 39 | * @return mixed 40 | */ 41 | public function init() 42 | { 43 | } 44 | 45 | /** 46 | * Returns the user-facing name. 47 | * 48 | * @return mixed 49 | */ 50 | public function getName() 51 | { 52 | return Craft::t('Airtable'); 53 | } 54 | 55 | /** 56 | * Plugins can have descriptions of themselves displayed on the Plugins page by adding a getDescription() method 57 | * on the primary plugin class: 58 | * 59 | * @return mixed 60 | */ 61 | public function getDescription() 62 | { 63 | return Craft::t('Use Airtable bases in Craft'); 64 | } 65 | 66 | /** 67 | * Plugins can have links to their documentation on the Plugins page by adding a getDocumentationUrl() method on 68 | * the primary plugin class: 69 | * 70 | * @return string 71 | */ 72 | public function getDocumentationUrl() 73 | { 74 | return 'https://github.com/mdxprograms/craft-airtable/blob/master/README.md'; 75 | } 76 | 77 | /** 78 | * Plugins can now take part in Craft’s update notifications, and display release notes on the Updates page, by 79 | * providing a JSON feed that describes new releases, and adding a getReleaseFeedUrl() method on the primary 80 | * plugin class. 81 | * 82 | * @return string 83 | */ 84 | public function getReleaseFeedUrl() 85 | { 86 | return 'https://github.com/mdxprograms/craft-airtable/blob/master/releases.json'; 87 | } 88 | 89 | /** 90 | * Returns the version number. 91 | * 92 | * @return string 93 | */ 94 | public function getVersion() 95 | { 96 | return '1.0.0'; 97 | } 98 | 99 | /** 100 | * As of Craft 2.5, Craft no longer takes the whole site down every time a plugin’s version number changes, in 101 | * case there are any new migrations that need to be run. Instead plugins must explicitly tell Craft that they 102 | * have new migrations by returning a new (higher) schema version number with a getSchemaVersion() method on 103 | * their primary plugin class: 104 | * 105 | * @return string 106 | */ 107 | public function getSchemaVersion() 108 | { 109 | return '1.0.0'; 110 | } 111 | 112 | /** 113 | * Returns the developer’s name. 114 | * 115 | * @return string 116 | */ 117 | public function getDeveloper() 118 | { 119 | return 'Josh Waller'; 120 | } 121 | 122 | /** 123 | * Returns the developer’s website URL. 124 | * 125 | * @return string 126 | */ 127 | public function getDeveloperUrl() 128 | { 129 | return 'https://www.joshwaller.me'; 130 | } 131 | 132 | /** 133 | * Returns whether the plugin should get its own tab in the CP header. 134 | * 135 | * @return bool 136 | */ 137 | public function hasCpSection() 138 | { 139 | return false; 140 | } 141 | 142 | /** 143 | * Called right before your plugin’s row gets stored in the plugins database table, and tables have been created 144 | * for it based on its records. 145 | */ 146 | public function onBeforeInstall() 147 | { 148 | } 149 | 150 | /** 151 | * Called right after your plugin’s row has been stored in the plugins database table, and tables have been 152 | * created for it based on its records. 153 | */ 154 | public function onAfterInstall() 155 | { 156 | } 157 | 158 | /** 159 | * Called right before your plugin’s record-based tables have been deleted, and its row in the plugins table 160 | * has been deleted. 161 | */ 162 | public function onBeforeUninstall() 163 | { 164 | } 165 | 166 | /** 167 | * Called right after your plugin’s record-based tables have been deleted, and its row in the plugins table 168 | * has been deleted. 169 | */ 170 | public function onAfterUninstall() 171 | { 172 | } 173 | 174 | /** 175 | * Defines the attributes that model your plugin’s available settings. 176 | * 177 | * @return array 178 | */ 179 | protected function defineSettings() 180 | { 181 | return array( 182 | 'api_key' => array(AttributeType::String, 'label' => 'API key', 'default' => ''), 183 | 'basesAndTables' => array(AttributeType::String, 'label' => 'Bases and Tables', 'default' => ''), 184 | ); 185 | } 186 | 187 | /** 188 | * Returns the HTML that displays your plugin’s settings. 189 | * 190 | * @return mixed 191 | */ 192 | public function getSettingsHtml() 193 | { 194 | return craft()->templates->render('airtable/Airtable_Settings', array( 195 | 'settings' => $this->getSettings() 196 | )); 197 | } 198 | 199 | /** 200 | * If you need to do any processing on your settings’ post data before they’re saved to the database, you can 201 | * do it with the prepSettings() method: 202 | * 203 | * @param mixed $settings The Widget's settings 204 | * 205 | * @return mixed 206 | */ 207 | public function prepSettings($settings) 208 | { 209 | // Modify $settings here... 210 | 211 | return $settings; 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Josh Waller 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Airtable plugin for Craft CMS 2 | 3 | Use Airtable bases in Craft 4 | 5 | ![Screenshot](http://www.chromegeek.com/wp-content/uploads/2016/02/Airtable-official-logo.png) 6 | 7 | ## Installation 8 | 9 | To install Airtable, follow these steps: 10 | 11 | 1. `cd /craft/plugins && git clone https://github.com/mdxprograms/craft-airtable.git airtable` 12 | 13 | Airtable works on Craft 2.4.x and Craft 2.5.x. 14 | 15 | ## Airtable Overview 16 | 17 | This plugin adds the ability to integrate your airtable bases and tables into 18 | craft cms. 19 | 20 | ## Configuring Airtable 21 | 22 | To add your settings, go to settings->plugins->airtable in craft admin panel. 23 | 24 | 1. You will need to supply your api key 25 | 2. Bases and Tables input takes the base id and then the table name. 26 | 27 | Ex: `appSdasdfindin@Products` 28 | 29 | You may add more by separating your entries with a `,` 30 | 31 | Ex: `appSdasdfindin@Products,appSdasdfindin@Categories` 32 | 33 | ## Using Airtable 34 | 35 | Once your settings are saved you will have access to a full crud api and some 36 | template variables as well. 37 | 38 | ### Twig template variables 39 | Find All records in a table: 40 | `craft.airtable.all('your_table_name')` 41 | 42 | Find a single record within a table: 43 | `craft.airtable.find('your_table_name', 'your_record_id')` 44 | 45 | List tables: 46 | `craft.airtable.tables()` 47 | 48 | Get a specific base by associated table: 49 | `craft.airtable.getBase('your_table_name')` 50 | 51 | ### API CRUD and form submissions 52 | Find All records (table name is required as a parameter): 53 | `GET: airtable/records/all` 54 | 55 | Find a single record within a table (table name and record id are required parameters): 56 | `GET: airtable/records/find` 57 | 58 | Save a record using a custom form: 59 | NOTE: you will need to supply a hidden field with the `name="table"` with your 60 | table name as the value. 61 | All form elements that you would like to post to will need to be added with 62 | `name="fields[your_field_name]"` 63 | 64 | You can also use the update action using the same form setup by just replacing 65 | the hidden action field value to: `airtable/records/update` and add a hidden field 66 | `name="recordId"` with the current record's id as the value. 67 | 68 | Example form save below: 69 | ``` 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | ``` 84 | 85 | Delete record (`recordId` must be supplied via post data): 86 | `POST: airtable/records/delete` 87 | 88 | ## Airtable Roadmap 89 | 90 | Some things to do, and ideas for potential features: 91 | 92 | * Generate forms based on table fields with a twig template generated 93 | 94 | ## Airtable Changelog 95 | 96 | ### 1.0.0 -- 2016.12.5 97 | 98 | * Initial release 99 | 100 | Brought to you by [Josh Waller](https://www.joshwaller.me) 101 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdxprograms/airtable", 3 | "description": "Use Airtable bases in Craft", 4 | "type": "craft-plugin", 5 | "authors": [ 6 | { 7 | "name": "Josh Waller", 8 | "homepage": "https://www.joshwaller.me" 9 | } 10 | ], 11 | "require": { 12 | "composer/installers": "~1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /controllers/Airtable_RecordsController.php: -------------------------------------------------------------------------------- 1 | request->isAjaxRequest()) { 43 | $records = craft()->airtable_record->all(); 44 | return $this->returnJson($records); 45 | } 46 | } 47 | 48 | // GET only action = airtable/records/find 49 | public function actionFind() 50 | { 51 | if (craft()->request->isAjaxRequest()) { 52 | $table = craft()->request->getParam('table'); 53 | $id = craft()->request->getParam('recordId'); 54 | $records = craft()->airtable_record->find($table, $recordId); 55 | return $this->returnJson($records); 56 | } 57 | } 58 | 59 | // POST action = airtable/records/save 60 | public function actionSave() 61 | { 62 | $this->requirePostRequest(); 63 | $formData = craft()->request->getPost(); 64 | $result = craft()->airtable_record->save($formData); 65 | 66 | if (craft()->request->isAjaxRequest()) { 67 | $this->returnJson($result); 68 | } else { 69 | $this->redirectToPostedUrl($result); 70 | } 71 | } 72 | 73 | // POST action = airtable/records/update 74 | public function actionUpdate() 75 | { 76 | $this->requirePostRequest(); 77 | $formData = craft()->request->getPost(); 78 | $result = craft()->airtable_record->update($formData); 79 | 80 | if (craft()->request->isAjaxRequest()) { 81 | $this->returnJson($result); 82 | } else { 83 | $this->redirectToPostedUrl($result); 84 | } 85 | } 86 | 87 | // POST action = airtable/records/delete 88 | public function actionDelete() 89 | { 90 | $this->requirePostRequest(); 91 | $id = craft()->request->getPost('recordId'); 92 | $result = craft()->airtable_record->delete($id); 93 | 94 | if (craft()->request->isAjaxRequest()) { 95 | $this->returnJson($result); 96 | } else { 97 | $this->redirectToPostedUrl($result); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /models/Airtable_RecordModel.php: -------------------------------------------------------------------------------- 1 | array(AttributeType::String, 'default' => 'some value'), 34 | )); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /releases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": "1.0.0", 4 | "downloadUrl": "https://github.com/mdxprograms/airtable/archive/master.zip", 5 | "date": "2016-11-28T15:43:45.207Z", 6 | "notes": [ 7 | "[Added] Initial release" 8 | ] 9 | } 10 | ] -------------------------------------------------------------------------------- /resources/css/Airtable_Style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Airtable plugin for Craft CMS 3 | * 4 | * Airtable CSS 5 | * 6 | * @author Josh Waller 7 | * @copyright Copyright (c) 2016 Josh Waller 8 | * @link https://www.joshwaller.me 9 | * @package Airtable 10 | * @since 1.0.0 11 | */ 12 | -------------------------------------------------------------------------------- /resources/css/widgets/Airtable_AirtableWidget.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Airtable plugin for Craft CMS 3 | * 4 | * Airtable_AirtableWidget CSS 5 | * 6 | * @author Josh Waller 7 | * @copyright Copyright (c) 2016 Josh Waller 8 | * @link https://www.joshwaller.me 9 | * @package Airtable 10 | * @since 1.0.0 11 | */ 12 | -------------------------------------------------------------------------------- /resources/icon-mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/images/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdxprograms/craft-airtable/0e60fba8146921fce611dcb90cfeb2fed878c5df/resources/images/plugin.png -------------------------------------------------------------------------------- /resources/js/Airtable_Script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Airtable plugin for Craft CMS 3 | * 4 | * Airtable JS 5 | * 6 | * @author Josh Waller 7 | * @copyright Copyright (c) 2016 Josh Waller 8 | * @link https://www.joshwaller.me 9 | * @package Airtable 10 | * @since 1.0.0 11 | */ 12 | -------------------------------------------------------------------------------- /resources/js/widgets/Airtable_AirtableWidget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Airtable plugin for Craft CMS 3 | * 4 | * Airtable_AirtableWidget JS 5 | * 6 | * @author Josh Waller 7 | * @copyright Copyright (c) 2016 Josh Waller 8 | * @link https://www.joshwaller.me 9 | * @package Airtable 10 | * @since 1.0.0 11 | */ 12 | -------------------------------------------------------------------------------- /resources/screenshots/plugin_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdxprograms/craft-airtable/0e60fba8146921fce611dcb90cfeb2fed878c5df/resources/screenshots/plugin_logo.png -------------------------------------------------------------------------------- /services/Airtable_BaseService.php: -------------------------------------------------------------------------------- 1 | airtable_base->tables() 26 | */ 27 | public function __construct() 28 | { 29 | $settings = craft()->plugins->getPlugin('airtable')->getSettings(); 30 | $this->token = $settings->api_key; 31 | $this->basesAndTables = explode(",", $settings->basesAndTables); 32 | $this->tables = array(); 33 | $this->setCurrentData(); 34 | } 35 | 36 | public function endpoint() 37 | { 38 | return "https://api.airtable.com/v0/"; 39 | } 40 | 41 | public function token() 42 | { 43 | return $this->token; 44 | } 45 | 46 | public function tables() 47 | { 48 | return array_values($this->tables); 49 | } 50 | 51 | public function getBase($table=null) 52 | { 53 | if ($table) { 54 | return array_search($table, $this->tables); 55 | } 56 | return "Please provide a table name"; 57 | } 58 | 59 | private function setCurrentData() 60 | { 61 | foreach ($this->basesAndTables as $bt) { 62 | $base = explode('@', $bt)[0]; 63 | $table = explode('@', $bt)[1]; 64 | $this->tables[$base] = $table; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /services/Airtable_RecordService.php: -------------------------------------------------------------------------------- 1 | airtable_record->exampleService() 24 | */ 25 | private $token; 26 | private $tables; 27 | private $endpoint; 28 | 29 | public function __construct() 30 | { 31 | $this->token = craft()->airtable_base->token(); 32 | $this->tables = craft()->airtable_base->tables(); 33 | $this->endpoint = craft()->airtable_base->endpoint(); 34 | } 35 | 36 | // ORM functions 37 | public function all($table=null) 38 | { 39 | return $this->fetch($table); 40 | } 41 | 42 | public function find($table=null, $id=null) 43 | { 44 | return $this->fetch($table, $id); 45 | } 46 | 47 | public function save($data=null) 48 | { 49 | return $this->post($data); 50 | } 51 | 52 | public function update($data=null) 53 | { 54 | return $this->put($data); 55 | } 56 | 57 | public function delete($table=null, $id=null) 58 | { 59 | return $this->destroy($table, $id); 60 | } 61 | 62 | // http handlers 63 | private function fetch($table, $id=null) 64 | { 65 | if (in_array($table, $this->tables)) { 66 | $base = craft()->airtable_base->getBase($table); 67 | $headers = array( 68 | 'Accept: application/json', 69 | 'Content-Type: application/json', 70 | "Authorization: Bearer $this->token" 71 | ); 72 | 73 | if ($id) { 74 | $endpoint = $this->endpoint . $base . '/' . $table . '/' . $id; 75 | } else { 76 | $endpoint = $this->endpoint . $base . '/' . $table; 77 | } 78 | 79 | $ch = curl_init(); 80 | curl_setopt($ch, CURLOPT_URL, $endpoint); 81 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 82 | curl_setopt($ch, CURLOPT_HEADER, 0); 83 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); 84 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 85 | $returnData = curl_exec($ch); 86 | $results = json_decode($returnData); 87 | curl_close($ch); 88 | 89 | if ($id) { 90 | return $results; 91 | } else { 92 | return $results->records; 93 | } 94 | } 95 | return "$table does not exist in Airtable"; 96 | } 97 | 98 | private function post($data) 99 | { 100 | if (in_array($data['table'], $this->tables)) { 101 | $base = craft()->airtable_base->getBase($data['table']); 102 | if ($data['fields']) { 103 | $headers = array( 104 | 'Accept: application/json', 105 | 'Content-Type: application/json', 106 | "Authorization: Bearer $this->token" 107 | ); 108 | $endpoint = $this->endpoint . $base . '/' . $data['table']; 109 | $ch = curl_init(); 110 | curl_setopt($ch, CURLOPT_URL, $endpoint); 111 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 112 | curl_setopt($ch, CURLOPT_HEADER, 0); 113 | curl_setopt($ch, CURLOPT_POST, 1); 114 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode( 115 | array('fields' => $data['fields'])) 116 | ); 117 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 118 | $returnData = curl_exec ($ch); 119 | $result = json_decode($returnData); 120 | curl_close ($ch); 121 | 122 | return $result; 123 | } 124 | return "Must supply a fields array with form data: name='fields[First Name]'"; 125 | } 126 | return $data['table'] . "does not exist in Airtable"; 127 | } 128 | 129 | private function put($data) 130 | { 131 | if (in_array($data['table'], $this->tables)) { 132 | $base = craft()->airtable_base->getBase($data['table']); 133 | if ($data['fields']) { 134 | $headers = array( 135 | 'Accept: application/json', 136 | 'Content-Type: application/json', 137 | "Authorization: Bearer $this->token" 138 | ); 139 | $endpoint = $this->endpoint . $base . '/' . $data['table']; 140 | $ch = curl_init(); 141 | curl_setopt($ch, CURLOPT_URL, $endpoint); 142 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 143 | curl_setopt($ch, CURLOPT_HEADER, 0); 144 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); 145 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode( 146 | array('fields' => $data['fields'])) 147 | ); 148 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 149 | $returnData = curl_exec ($ch); 150 | $result = json_decode($returnData); 151 | curl_close ($ch); 152 | 153 | return $result; 154 | } 155 | return "Must supply a fields array with form data: name='fields[First Name]'"; 156 | } 157 | return $data['table'] . "does not exist in Airtable"; 158 | } 159 | 160 | private function destroy($table, $id) 161 | { 162 | if (in_array($table, $this->tables)) { 163 | $base = craft()->airtable_base->getBase($table); 164 | if ($id) { 165 | $headers = array( 166 | 'Accept: application/json', 167 | 'Content-Type: application/json', 168 | "Authorization: Bearer $this->token" 169 | ); 170 | $endpoint = $this->endpoint . $base . '/' . $table . '/' . $id; 171 | $ch = curl_init(); 172 | curl_setopt($ch, CURLOPT_URL, $endpoint); 173 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 174 | curl_setopt($ch, CURLOPT_HEADER, 0); 175 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); 176 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 177 | $returnData = curl_exec ($ch); 178 | $result = json_decode($returnData); 179 | curl_close ($ch); 180 | 181 | return $result; 182 | } 183 | return "Must supply a table and an id"; 184 | } 185 | return "$table does not exist in Airtable"; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /templates/Airtable_Settings.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * Airtable plugin for Craft CMS 4 | * 5 | * Airtable Settings.twig 6 | * 7 | * @author Josh Waller 8 | * @copyright Copyright (c) 2016 Josh Waller 9 | * @link https://www.joshwaller.me 10 | * @package Airtable 11 | * @since 1.0.0 12 | */ 13 | #} 14 | 15 | {% import "_includes/forms" as forms %} 16 | 17 | {% includeCssResource "airtable/css/Airtable_Style.css" %} 18 | {% includeJsResource "airtable/js/Airtable_Script.js" %} 19 | 20 | {{ forms.textField({ 21 | label: 'API Key', 22 | instructions: 'Enter your API key', 23 | id: 'api_key', 24 | name: 'api_key', 25 | value: settings['api_key']}) 26 | }} 27 | 28 | {{ forms.textField({ 29 | label: 'Bases and Tables', 30 | instructions: 'Enter all bases ids separated by a @ then your table name as shown in Airtable. Multiple Base and tables can be added by adding a , separator.
Example: 123412341234@Candidates, alksdfjhq23@Users', 31 | id: 'basesAndTables', 32 | name: 'basesAndTables', 33 | value: settings['basesAndTables']}) 34 | }} 35 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | 3 | {% set title = "Airtable Settings"|t %} 4 | 5 | {% set content %} 6 | {% include 'airtable/Airtable_Settings' %} 7 | {% endset %} 8 | -------------------------------------------------------------------------------- /templates/widgets/Airtable_AirtableWidget_Body.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * Airtable plugin for Craft CMS 4 | * 5 | * Airtable_AirtableWidget Body 6 | * 7 | * @author Josh Waller 8 | * @copyright Copyright (c) 2016 Josh Waller 9 | * @link https://www.joshwaller.me 10 | * @package Airtable 11 | * @since 1.0.0 12 | */ 13 | #} 14 | -------------------------------------------------------------------------------- /templates/widgets/Airtable_AirtableWidget_Settings.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * Airtable plugin for Craft CMS 4 | * 5 | * Airtable_AirtableWidget Settings 6 | * 7 | * @author Josh Waller 8 | * @copyright Copyright (c) 2016 Josh Waller 9 | * @link https://www.joshwaller.me 10 | * @package Airtable 11 | * @since 1.0.0 12 | */ 13 | #} 14 | 15 | {% import "_includes/forms" as forms %} 16 | 17 | {% includeCssResource "airtable/css/Airtable_Style.css" %} 18 | {% includeJsResource "airtable/js/Airtable_Script.js" %} 19 | 20 | {{ forms.textField({ 21 | label: 'Some Setting', 22 | instructions: 'Enter some setting here.', 23 | id: 'someSetting', 24 | name: 'someSetting', 25 | value: settings['someSetting']}) 26 | }} 27 | -------------------------------------------------------------------------------- /translations/en.php: -------------------------------------------------------------------------------- 1 | 'To this', 16 | ); 17 | -------------------------------------------------------------------------------- /variables/AirtableVariable.php: -------------------------------------------------------------------------------- 1 | airtable_base->tables(); 24 | } 25 | 26 | public function getBase($table=null) 27 | { 28 | return craft()->airtable_base->getBase($table); 29 | } 30 | 31 | public function all($table=null) 32 | { 33 | return craft()->airtable_record->all($table); 34 | } 35 | 36 | public function find($table=null, $id=null) 37 | { 38 | return craft()->airtable_record->find($table, $id); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | 57 | private $classMapAuthoritative = false; 58 | 59 | public function getPrefixes() 60 | { 61 | if (!empty($this->prefixesPsr0)) { 62 | return call_user_func_array('array_merge', $this->prefixesPsr0); 63 | } 64 | 65 | return array(); 66 | } 67 | 68 | public function getPrefixesPsr4() 69 | { 70 | return $this->prefixDirsPsr4; 71 | } 72 | 73 | public function getFallbackDirs() 74 | { 75 | return $this->fallbackDirsPsr0; 76 | } 77 | 78 | public function getFallbackDirsPsr4() 79 | { 80 | return $this->fallbackDirsPsr4; 81 | } 82 | 83 | public function getClassMap() 84 | { 85 | return $this->classMap; 86 | } 87 | 88 | /** 89 | * @param array $classMap Class to filename map 90 | */ 91 | public function addClassMap(array $classMap) 92 | { 93 | if ($this->classMap) { 94 | $this->classMap = array_merge($this->classMap, $classMap); 95 | } else { 96 | $this->classMap = $classMap; 97 | } 98 | } 99 | 100 | /** 101 | * Registers a set of PSR-0 directories for a given prefix, either 102 | * appending or prepending to the ones previously set for this prefix. 103 | * 104 | * @param string $prefix The prefix 105 | * @param array|string $paths The PSR-0 root directories 106 | * @param bool $prepend Whether to prepend the directories 107 | */ 108 | public function add($prefix, $paths, $prepend = false) 109 | { 110 | if (!$prefix) { 111 | if ($prepend) { 112 | $this->fallbackDirsPsr0 = array_merge( 113 | (array) $paths, 114 | $this->fallbackDirsPsr0 115 | ); 116 | } else { 117 | $this->fallbackDirsPsr0 = array_merge( 118 | $this->fallbackDirsPsr0, 119 | (array) $paths 120 | ); 121 | } 122 | 123 | return; 124 | } 125 | 126 | $first = $prefix[0]; 127 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 128 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 129 | 130 | return; 131 | } 132 | if ($prepend) { 133 | $this->prefixesPsr0[$first][$prefix] = array_merge( 134 | (array) $paths, 135 | $this->prefixesPsr0[$first][$prefix] 136 | ); 137 | } else { 138 | $this->prefixesPsr0[$first][$prefix] = array_merge( 139 | $this->prefixesPsr0[$first][$prefix], 140 | (array) $paths 141 | ); 142 | } 143 | } 144 | 145 | /** 146 | * Registers a set of PSR-4 directories for a given namespace, either 147 | * appending or prepending to the ones previously set for this namespace. 148 | * 149 | * @param string $prefix The prefix/namespace, with trailing '\\' 150 | * @param array|string $paths The PSR-4 base directories 151 | * @param bool $prepend Whether to prepend the directories 152 | * 153 | * @throws \InvalidArgumentException 154 | */ 155 | public function addPsr4($prefix, $paths, $prepend = false) 156 | { 157 | if (!$prefix) { 158 | // Register directories for the root namespace. 159 | if ($prepend) { 160 | $this->fallbackDirsPsr4 = array_merge( 161 | (array) $paths, 162 | $this->fallbackDirsPsr4 163 | ); 164 | } else { 165 | $this->fallbackDirsPsr4 = array_merge( 166 | $this->fallbackDirsPsr4, 167 | (array) $paths 168 | ); 169 | } 170 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 171 | // Register directories for a new namespace. 172 | $length = strlen($prefix); 173 | if ('\\' !== $prefix[$length - 1]) { 174 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 175 | } 176 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 177 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 178 | } elseif ($prepend) { 179 | // Prepend directories for an already registered namespace. 180 | $this->prefixDirsPsr4[$prefix] = array_merge( 181 | (array) $paths, 182 | $this->prefixDirsPsr4[$prefix] 183 | ); 184 | } else { 185 | // Append directories for an already registered namespace. 186 | $this->prefixDirsPsr4[$prefix] = array_merge( 187 | $this->prefixDirsPsr4[$prefix], 188 | (array) $paths 189 | ); 190 | } 191 | } 192 | 193 | /** 194 | * Registers a set of PSR-0 directories for a given prefix, 195 | * replacing any others previously set for this prefix. 196 | * 197 | * @param string $prefix The prefix 198 | * @param array|string $paths The PSR-0 base directories 199 | */ 200 | public function set($prefix, $paths) 201 | { 202 | if (!$prefix) { 203 | $this->fallbackDirsPsr0 = (array) $paths; 204 | } else { 205 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 206 | } 207 | } 208 | 209 | /** 210 | * Registers a set of PSR-4 directories for a given namespace, 211 | * replacing any others previously set for this namespace. 212 | * 213 | * @param string $prefix The prefix/namespace, with trailing '\\' 214 | * @param array|string $paths The PSR-4 base directories 215 | * 216 | * @throws \InvalidArgumentException 217 | */ 218 | public function setPsr4($prefix, $paths) 219 | { 220 | if (!$prefix) { 221 | $this->fallbackDirsPsr4 = (array) $paths; 222 | } else { 223 | $length = strlen($prefix); 224 | if ('\\' !== $prefix[$length - 1]) { 225 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 226 | } 227 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 228 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 229 | } 230 | } 231 | 232 | /** 233 | * Turns on searching the include path for class files. 234 | * 235 | * @param bool $useIncludePath 236 | */ 237 | public function setUseIncludePath($useIncludePath) 238 | { 239 | $this->useIncludePath = $useIncludePath; 240 | } 241 | 242 | /** 243 | * Can be used to check if the autoloader uses the include path to check 244 | * for classes. 245 | * 246 | * @return bool 247 | */ 248 | public function getUseIncludePath() 249 | { 250 | return $this->useIncludePath; 251 | } 252 | 253 | /** 254 | * Turns off searching the prefix and fallback directories for classes 255 | * that have not been registered with the class map. 256 | * 257 | * @param bool $classMapAuthoritative 258 | */ 259 | public function setClassMapAuthoritative($classMapAuthoritative) 260 | { 261 | $this->classMapAuthoritative = $classMapAuthoritative; 262 | } 263 | 264 | /** 265 | * Should class lookup fail if not found in the current class map? 266 | * 267 | * @return bool 268 | */ 269 | public function isClassMapAuthoritative() 270 | { 271 | return $this->classMapAuthoritative; 272 | } 273 | 274 | /** 275 | * Registers this instance as an autoloader. 276 | * 277 | * @param bool $prepend Whether to prepend the autoloader or not 278 | */ 279 | public function register($prepend = false) 280 | { 281 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 282 | } 283 | 284 | /** 285 | * Unregisters this instance as an autoloader. 286 | */ 287 | public function unregister() 288 | { 289 | spl_autoload_unregister(array($this, 'loadClass')); 290 | } 291 | 292 | /** 293 | * Loads the given class or interface. 294 | * 295 | * @param string $class The name of the class 296 | * @return bool|null True if loaded, null otherwise 297 | */ 298 | public function loadClass($class) 299 | { 300 | if ($file = $this->findFile($class)) { 301 | includeFile($file); 302 | 303 | return true; 304 | } 305 | } 306 | 307 | /** 308 | * Finds the path to the file where the class is defined. 309 | * 310 | * @param string $class The name of the class 311 | * 312 | * @return string|false The path if found, false otherwise 313 | */ 314 | public function findFile($class) 315 | { 316 | // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 317 | if ('\\' == $class[0]) { 318 | $class = substr($class, 1); 319 | } 320 | 321 | // class map lookup 322 | if (isset($this->classMap[$class])) { 323 | return $this->classMap[$class]; 324 | } 325 | if ($this->classMapAuthoritative) { 326 | return false; 327 | } 328 | 329 | $file = $this->findFileWithExtension($class, '.php'); 330 | 331 | // Search for Hack files if we are running on HHVM 332 | if ($file === null && defined('HHVM_VERSION')) { 333 | $file = $this->findFileWithExtension($class, '.hh'); 334 | } 335 | 336 | if ($file === null) { 337 | // Remember that this class does not exist. 338 | return $this->classMap[$class] = false; 339 | } 340 | 341 | return $file; 342 | } 343 | 344 | private function findFileWithExtension($class, $ext) 345 | { 346 | // PSR-4 lookup 347 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 348 | 349 | $first = $class[0]; 350 | if (isset($this->prefixLengthsPsr4[$first])) { 351 | foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { 352 | if (0 === strpos($class, $prefix)) { 353 | foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 354 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 355 | return $file; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | // PSR-4 fallback dirs 363 | foreach ($this->fallbackDirsPsr4 as $dir) { 364 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 365 | return $file; 366 | } 367 | } 368 | 369 | // PSR-0 lookup 370 | if (false !== $pos = strrpos($class, '\\')) { 371 | // namespaced class name 372 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 373 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 374 | } else { 375 | // PEAR-like class name 376 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 377 | } 378 | 379 | if (isset($this->prefixesPsr0[$first])) { 380 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 381 | if (0 === strpos($class, $prefix)) { 382 | foreach ($dirs as $dir) { 383 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 384 | return $file; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | 391 | // PSR-0 fallback dirs 392 | foreach ($this->fallbackDirsPsr0 as $dir) { 393 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 394 | return $file; 395 | } 396 | } 397 | 398 | // PSR-0 include paths. 399 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 400 | return $file; 401 | } 402 | } 403 | } 404 | 405 | /** 406 | * Scope isolated include. 407 | * 408 | * Prevents access to $this/self from included files. 409 | */ 410 | function includeFile($file) 411 | { 412 | include $file; 413 | } 414 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2016 Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION'); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInite059ca3a93cff39fd6deca757d0158fa::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | templates->render(). 35 | * 36 | * @return mixed 37 | */ 38 | public function getBodyHtml() 39 | { 40 | // Include our Javascript & CSS 41 | craft()->templates->includeCssResource('airtable/css/widgets/Airtable_AirtableWidget.css'); 42 | craft()->templates->includeJsResource('airtable/js/widgets/Airtable_AirtableWidget.js'); 43 | /* -- Variables to pass down to our rendered template */ 44 | $variables = array(); 45 | $variables['settings'] = $this->getSettings(); 46 | return craft()->templates->render('airtable/widgets/Airtable_AirtableWidget_Body', $variables); 47 | } 48 | /** 49 | * Returns how many columns the widget will span in the Admin CP 50 | * 51 | * @return int 52 | */ 53 | public function getColspan() 54 | { 55 | return 1; 56 | } 57 | /** 58 | * Defines the attributes that model your Widget's available settings. 59 | * 60 | * @return array 61 | */ 62 | protected function defineSettings() 63 | { 64 | return array( 65 | 'someSetting' => array(AttributeType::String, 'label' => 'Some Setting', 'default' => ''), 66 | ); 67 | } 68 | /** 69 | * Returns the HTML that displays your Widget's settings. 70 | * 71 | * @return mixed 72 | */ 73 | public function getSettingsHtml() 74 | { 75 | 76 | /* -- Variables to pass down to our rendered template */ 77 | 78 | $variables = array(); 79 | $variables['settings'] = $this->getSettings(); 80 | return craft()->templates->render('airtable/widgets/Airtable_AirtableWidget_Settings',$variables); 81 | } 82 | 83 | /** 84 | * If you need to do any processing on your settings’ post data before they’re saved to the database, you can 85 | * do it with the prepSettings() method: 86 | * 87 | * @param mixed $settings The Widget's settings 88 | * 89 | * @return mixed 90 | */ 91 | public function prepSettings($settings) 92 | { 93 | 94 | /* -- Modify $settings here... */ 95 | 96 | return $settings; 97 | } 98 | } --------------------------------------------------------------------------------