├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src └── CodaPHP └── CodaPHP.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel Stieber 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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 | CodaPHP 2 | ======================= 3 | [![Latest Stable](https://img.shields.io/github/release/danielstieber/codaphp.svg?style=flat-square)](https://github.com/danielstieber/codaphp/releases) 4 | [![Coda API Version](https://img.shields.io/badge/Coda_API_version-1.3.0-orange.svg?style=flat-square)](https://coda.io/developers/apis/v1) 5 | ![Downloads](https://img.shields.io/packagist/dt/danielstieber/coda-php?style=flat-square) 6 | 7 | * [Quickstart](#Quickstart) 8 | * [Detailed Documentation](#Documentation) 9 | * [Doc & Pack Analytics](#Analytics) 10 | * [Caching](#Caching) 11 | * [Changelog](#Changelog) 12 | 13 | CodaPHP is a library that makes it easy to use data from [Coda](https://www.coda.io) docs in web projects by using the [Coda API](https://coda.io/developers/apis/v1). 14 | 15 | Easily use all available API calls with one library including 16 | * List all documents 17 | * Read data from tables, formulas and controls 18 | * Add/modify rows 19 | * Manage doc permissions 20 | * and a lot more 21 | 22 | → [**Get 10$ discount on Coda paid plans when signing up with this link**](https://coda.io/?r=Qjx7OzpmTa2L6IPfkY-anw) 23 | 24 | ## Quickstart 25 | ### Installation and basic usage 26 | Install the library through [Composer](http://getcomposer.org/): 27 | ```bash 28 | php composer.phar require danielstieber/coda-php 29 | ``` 30 | and add it to your project: 31 | ```PHP 32 | require './vendor/autoload.php'; 33 | $coda = new CodaPHP\CodaPHP(''); 34 | 35 | // List all your docs 36 | $result = $coda->listDocs(); 37 | var_dump($result); 38 | ``` 39 | 40 | ### Handling table data 41 | Let's assume you have the table 'Products' in your Coda doc: 42 | #### Products 43 | | Title ⚑ | Price | Status | 44 | |-----------|-------|-------------| 45 | | Goatmilk | 14.90 | available ▼ | 46 | | Goatmeat | 38.90 | available ▼ | 47 | 48 | ```PHP 49 | // Get the price of the goatmilk 50 | $docId = $coda->getDocId(''); 51 | 52 | // Lists only Products with status 'available' (currently only one filter allowed) 53 | $availableProducts = $coda->listRows($docId, 'Products', ['query' => ['status' => 'available']]); 54 | 55 | // Show value of one cell 56 | $result = $coda->getRow($docId, 'Products', 'Goatmilk'); 57 | var_dump($result['values']['Price']); 58 | // Will show you 'float(14.90)' 59 | 60 | // Add the new product 'Goatcheese' 61 | if($coda->insertRows($docId, 'Products', ['Title' => 'Goatcheese', 'Price' => 23.50, 'Status' => 'available'])) { 62 | echo 'Product added'; 63 | } 64 | 65 | // Change the status of the product 'Goatmilk' to 'sold out' 66 | if($coda->insertRows($docId, 'Products', ['Title' => 'Goatmilk', 'Status' => 'sold out'], ['Title'])) { 67 | echo 'Product updated'; 68 | } 69 | ``` 70 | ### Triggering automations 71 | Since May 2022, Coda automations can be triggered via webhooks – and via CodaPHP. To trigger an automation, the automation must be set to "Webhook invoked". To run the automation you need the doc ID an the ID of the automation rule. You can find the rule ID when you click on the 3 dots (kebap menu) above the rule step settings. 72 | 73 | ```PHP 74 | // Trigger the automation 75 | $result = $coda->runAutomation('', ''); 76 | ``` 77 | 78 | ## Overview 79 | This is a personal side project. If you have any suggestions, find bugs or want to contribute, don't hesitate to contact me. You can use the [offical Coda community](https://community.coda.io/) to asks questions and reach out as well. 80 | 81 | ### Token 82 | Generate your token in the Coda profile settings. *Notice: Everyone with this token has full access to all your docs!* 83 | 84 | ### Methods 85 | The method names are inspired by the wording of the [official Coda API documentation](https://coda.io/developers/apis/v1beta1) and are listed below. 86 | 87 | ### Parameters 88 | All parameters can be found in the [official Coda API documentation](https://coda.io/developers/apis/v1beta1). Just add an associative array with your parameters to selected functions. The parameter _useColumnNames_ is set true by default in all 'row' functions. I list the important ones below. 89 | 90 | ### Response 91 | In case of success, responses are mostly untouched but converted to PHP arrays. Exception is `insertRow()` function, which provides a boolean true in case of success. 92 | In case of an error, the response includes the statusCode and provided error message, also untouched and converted to an array. 93 | 94 | ### Cache data 95 | Every API call may take a few seconds. It is recommended to store results and only call for new when necessary. The library provides a simple caching mechanic to store received data in a .codaphp_cache folder. **This mechanic is optional** and needs to be activated. Learn more in the [caching instructions](#Caching) 96 | 97 | ## Documentation 98 | ```PHP 99 | $coda = new CodaPHP(''); // Create instance of CodaPHP 100 | ``` 101 | ### Docs 102 | ```PHP 103 | $coda->getDocId(''); // Get the id of a doc 104 | 105 | $coda->listDocs(); // List all docs you have access to 106 | $coda->listDocs(['query' => 'todo']); // List docs filtered by searchquery 'todo' 107 | $coda->getDoc(''); // Get a specific doc 108 | $coda->createDoc('My new doc'); // Create a new doc 109 | $coda->createDoc('Copy of my old doc', ''); // Copy a doc 110 | ``` 111 | ### Pages (former Folders & Sections) 112 | ```PHP 113 | $coda->listPages(''); // List all sections in a doc 114 | $coda->getPage('', ''); // Get a section in a doc 115 | ``` 116 | ### Tables/Views, Columns and Rows 117 | ```PHP 118 | $coda->listTables(''); // List all tables in a doc 119 | $coda->getTable('', ''); // Get a table in a doc 120 | 121 | $coda->listColumns('', '
'); // List all columns in a table 122 | $coda->getColumn('', '
', ''); // Get a column in a table 123 | 124 | $coda->listRows('', '
'); // List all row in a table 125 | $coda->insertRows('', '
', [['' => '']], ['']); // Insert/updates a row in a table 126 | 127 | // Examples: 128 | $coda->insertRows('', 'todos', ['title' => 'Shower']); // Adds one row to 'todo' 129 | $coda->insertRows('', 'todos', [['title' => 'Wash dishes'], ['title' => 'Clean car']]); // Adds two rows to 'todo' 130 | $coda->insertRows('', 'todos', [['title' => 'Shower', 'status' => 'done'], ['title' => 'Buy goatcheese']], ['title']); // Updates the status of 'Shower' and inserts a new todo 131 | 132 | $coda->updateRow('', '
', '', ['' => '']); // Updates a row in a table 133 | $coda->getRow('', '
', ''); // Get a row in a table 134 | $coda->deleteRow('', '
', ''); // Deletes a row in a table 135 | ``` 136 | ### Working with Views 137 | Since Coda API Version 1.0.0 there are no seperate view methods. All view operations can be done via the table methods. 138 | ### Pushing Buttons 139 | ```PHP 140 | $coda->pushButton('', '
', '', '); // Pushes the button on the given column in a table 141 | ``` 142 | ### Formulas and Controls 143 | ```PHP 144 | $coda->listFormulas(''); // List all formulas in a doc 145 | $coda->getFormula('', ''); // Get a formula in a doc 146 | 147 | $coda->listControls(''); // List all controls in a doc 148 | $coda->getControl('', ''); //Get a control in a doc 149 | ``` 150 | ### Manage permissions 151 | ```PHP 152 | $coda->listPermissions(''); // Get information about users & permissions for a doc 153 | $coda->addUser('', ''); // Add a user to a doc (default permissions are 'write') 154 | $coda->addUser('', '', 'readonly', true); // Add a 'readonly' user and notify via email 155 | $coda->deleteUser('', ''); // Removes a user from the doc 156 | $coda->addPermission('', '', '', ''); // Add a permission to a doc 157 | $coda->deletePermission('', ''); // Remove a permission from a doc 158 | $coda->getACLMetadata(''); // Returns the ACL metadata of a doc 159 | ``` 160 | Learn more about permission settings with the API [here](https://coda.io/developers/apis/v1#tag/ACLs). 161 | ### Run automations / trigger webhooks 162 | ```PHP 163 | $coda->runAutomation('', ''); 164 | ``` 165 | 166 | ### Account and other 167 | ```PHP 168 | $coda->whoAmI(); // Get information about the current account 169 | $coda->resolveLink(''); // Resolves a link 170 | $coda->getMutationStatus(''); // Resolves a link 171 | ``` 172 | 173 | ### Analytics 174 | ```PHP 175 | $coda->listDocAnalytics(); // List all docs with analytics data 176 | $coda->listDocAnalytics(['query' => 'Goats', 'sinceDate' => '2022-08-02', 'sinceDate' => '2022-08-04']); // List docs about "Goats" with analytics data between 2nd and 4th of August 2022 177 | // All parameters for all methods can be found in the official API docs: https://coda.io/developers/apis/v1#tag/Analytics/operation/listDocAnalytics 178 | $coda->listPageAnalytics(''); // List analytics data on page level of given doc 179 | $coda->listPackAnalytics(); // List all packs where user has edit rights with analytics data 180 | $coda->listPackAnalytics(['workspaceId' => 'ws-123Ave']); // List all packs where user has edit rights with analytics data from workspace ws-123Ave 181 | $coda->listPackFormulaAnalytics('getDocAnalyticsSummery(); // Returns summarized analytics data for available docs. 184 | $coda->getPackAnalyticsSummery(); // Returns summarized analytics data for available packs. 185 | 186 | $coda->getAnalyticsUpdatedDay(); // Returns days based on Pacific Standard Time when analytics were last updated. Usually 1 day ago PST. 187 | ``` 188 | 189 | ## Caching 190 | The library can cache API requests in JSON files. If caching is activated, the library tries to create a `.codaphp_cache` folder in your project root. If it can't create or find the folder, it will deactivate caching. You can also create the folder on your own and set CHMOD so the library can read & write files in it. Only doc data & content will be cached, no permissions, links or mutation status! 191 | ```PHP 192 | $coda = new CodaPHP('', '', ''); // Instance creation with otptional caching & expiry time 193 | $coda = new CodaPHP('', true); // Instance with activated caching 194 | ``` 195 | By default, the cache will expire after 7 days. You can manually change the expiry time. 196 | ```PHP 197 | $coda = new CodaPHP('', true, 86400); // Activate caching and set cache to 1 day (in seconds) 198 | ``` 199 | You can also clear the cache manually 200 | ```PHP 201 | $coda->clearCache(); // Clears the whole cache 202 | ``` 203 | #### Control cache inside the doc 204 | A simple way to control the cache status from the coda doc is a button that triggers the clear cache method. 205 | ```PHP 206 | $coda = new CodaPHP('', true); 207 | if(isset($_GET['clearCache'])) { 208 | $coda->clearCache(); 209 | } 210 | ``` 211 | Now you can add a "open hyperlink"-button in your doc that opens https://yourdomain.com/?clearCache. After clicking the button the website will receive the latest data and saves it in the cache again. 212 | ![clear cache button](https://i.imgur.com/it4rkxV.png) 213 | 214 | ## Changelog 215 | ### 0.4.0 (August 28, 2022) 216 | * Update to API version 1.3.0 217 | * New features: 218 | - Added Analytics endpoints 219 | * Bug fixes: 220 | - Improved getDocId() regex for better matching 221 | 222 | ### 0.3.0 (May 24, 2022) 223 | * Update to API version 1.2.6. 224 | * New features: 225 | - Trigger "Webhook invoked" automations 226 | 227 | ### 0.2.0 (January 3, 2021) 228 | * Update to API version 1.1.0. 229 | * New features: 230 | - Added ACL permission management 231 | - Added optional caching 232 | 233 | ### 0.1.0 (August 15, 2020) 234 | * Update to API version 1.0.0. 235 | * Breaking changes: 236 | - Sections & Folders have been replaced by pages 237 | - Removed 'view' methods. You can access views via table methods. 238 | * New features: 239 | - Added mutation status method 240 | You can read more about API version 1.0.0 [here](https://community.coda.io/t/launched-coda-api-v1/17248) 241 | 242 | ### 0.0.4 (February 16, 2020) 243 | * Updated to API version 0.2.4-beta. New features: 244 | - Pushing buttons inside of tables & views 245 | - Getting and interacting with views 246 | - Creating docs in folders 247 | - Ability to disable parsing of cell values 248 | 249 | ### 0.0.3 (March 16, 2019) 250 | * Fixed an issue with using queries in listRows (Thanks to [Al Chen](https://github.com/albertc44) from Coda for mentioning this) 251 | 252 | ### 0.0.2 (November 15, 2018) 253 | * Fixed an issue regarding table names with special characters (Thanks to Oleg from Coda for mentioning this) 254 | 255 | ### 0.0.1 (November 11, 2018) 256 | * Initial version based on v0.1.1-beta of Coda API -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "danielstieber/coda-php", 3 | "description": "CodaPHP is a library that makes it easy to use Coda API in web projects.", 4 | "keywords": ["library", "coda", "api"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Daniel Stieber", 10 | "email": "daniel.w.stieber@gmail.com", 11 | "homepage": "http://www.danielstieber.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.0.0", 16 | "guzzlehttp/guzzle": ">=6.0" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "CodaPHP": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CodaPHP/CodaPHP.php: -------------------------------------------------------------------------------- 1 | apiToken = $apiToken; 38 | $this->client = new Client([ 39 | 'headers' => [ 40 | 'Authorization' => 'Bearer '.$this->apiToken 41 | ] 42 | ]); 43 | if($cacheData && (is_dir(self::CACHE_DIR) || mkdir(self::CACHE_DIR))) { 44 | // caching is only available when the cache folder exists / can be created 45 | $this->cacheData = $cacheData; 46 | } else { 47 | $this->cacheData = false; 48 | } 49 | $this->maxAge = $maxAge; 50 | } 51 | /** 52 | * Performs a request using guzzle 53 | * 54 | * @param string $url 55 | * @param array $params Guzzle request params 56 | * @param string $method HTTP request method 57 | * @param bool $addstatus When true, the return will include the HTTP status code 58 | * @param bool $ignoreCache When true, the caching option will be ignored 59 | * @return array 60 | */ 61 | protected function request($url, array $params = [], $method = 'GET', $addStatus = false, $ignoreCache = false) 62 | { 63 | $cacheFile = self::CACHE_DIR . md5(json_encode([$url => $params])) . '.json'; 64 | if($method == 'GET' && $ignoreCache === false && $this->cacheData === true && file_exists($cacheFile)) { // checks for cached response 65 | $cache = json_decode(file_get_contents($cacheFile), true); 66 | if((time() - $cache[0]) <= $this->maxAge) { 67 | $httpCode = $cache[1]; 68 | $dataArray = $cache[2]; 69 | if($addStatus) { 70 | return ['statusCode' => $httpCode, 'result' => $dataArray]; 71 | } else { 72 | return $dataArray; 73 | } 74 | } else { 75 | unlink($cacheFile); 76 | } 77 | } 78 | 79 | try { 80 | $response = $this->client->request($method, self::API_BASE . $url, $params); 81 | } catch (BadResponseException $e) { 82 | $errorContent = $e->getResponse()->getBody()->getContents(); 83 | return(json_decode($errorContent, true)); 84 | } 85 | 86 | $httpCode = $response->getStatusCode(); 87 | $responseString = $response->getBody()->getContents(); 88 | $dataArray = json_decode($responseString, true); 89 | 90 | if($method == 'GET' && $ignoreCache === false && $this->cacheData === true) { // caches response 91 | $cache = [time(), $httpCode, $dataArray]; 92 | $filecreation = file_put_contents($cacheFile, json_encode($cache)); 93 | } 94 | 95 | if (is_array($dataArray) && JSON_ERROR_NONE === json_last_error()) { 96 | if($addStatus) { 97 | return ['statusCode' => $httpCode, 'result' => $dataArray]; 98 | } else { 99 | return $dataArray; 100 | } 101 | } 102 | throw new \UnexpectedValueException('Invalid JSON: ' . $responseString); 103 | } 104 | /** 105 | * Returns the Coda doc id of a Coda doc url 106 | * 107 | * @param string $url Coda doc url 108 | * @return string 10-digit Coda doc id 109 | */ 110 | public static function getDocId($url) { 111 | $re = '/coda.io\/d\/.*?_d(.{10})\/*/m'; 112 | preg_match($re, $url, $res); 113 | return $res[1] ?? false; 114 | } 115 | /** 116 | * Returns infos about the user 117 | * 118 | * @return array 119 | */ 120 | public function whoAmI() 121 | { 122 | $res = $this->request('/whoami'); 123 | return $res; 124 | } 125 | /** 126 | * Returns an array of docs 127 | * 128 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listDocs 129 | * @return array 130 | */ 131 | public function listDocs(array $params = []) 132 | { 133 | $res = $this->request('/docs?'.http_build_query($params)); 134 | return $res; 135 | } 136 | /** 137 | * Returns information of a doc 138 | * 139 | * @param string $doc Id of a doc 140 | * @return array 141 | */ 142 | public function getDoc($doc) 143 | { 144 | $res = $this->request('/docs/'.$doc); 145 | return $res; 146 | } 147 | /** 148 | * Creates a new doc 149 | * 150 | * @param string $title Name of the doc 151 | * @param string $source Id of a doc to use as source (creates a copy) 152 | * @return array 153 | */ 154 | public function createDoc($title = '', $source = '', $folderId = '', $timezone = '') 155 | { 156 | $params['title'] = $title; 157 | $params['sourceDoc'] = $source; 158 | $params['folderId'] = $folderId; 159 | $params['timezone'] = $timezone; 160 | $res = $this->request('/docs', ['json' => $params], 'POST'); 161 | return $res; 162 | } 163 | /** 164 | * Returns pages in a doc 165 | * 166 | * @param string $doc Id of a doc 167 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listPages 168 | * @return array 169 | */ 170 | public function listPages($doc, array $params = []) 171 | { 172 | $res = $this->request('/docs/'.$doc.'/pages?'.http_build_query($params)); 173 | return $res; 174 | } 175 | /** 176 | * Returns a page in a doc 177 | * 178 | * @param string $doc Id of a doc 179 | * @param string $page Id or name of a page 180 | * @return array 181 | */ 182 | public function getPage($doc, $page) 183 | { 184 | $res = $this->request('/docs/'.$doc.'/pages/'.$this->prepareStrings($page)); 185 | return $res; 186 | } 187 | /** 188 | * Returns tables or views in a doc 189 | * 190 | * @param string $doc Id of a doc 191 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listTables 192 | * @return array 193 | */ 194 | public function listTables($doc, array $params = []) 195 | { 196 | $res = $this->request('/docs/'.$doc.'/tables?'.http_build_query($params)); 197 | return $res; 198 | } 199 | /** 200 | * Returns a table or a view in a doc 201 | * 202 | * @param string $doc Id of a doc 203 | * @param string $table Id or name of a table or view 204 | * @return array 205 | */ 206 | public function getTable($doc, $table) 207 | { 208 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table)); 209 | return $res; 210 | } 211 | /** 212 | * Returns columns in a table or view 213 | * 214 | * @param string $doc Id of a doc 215 | * @param string $table Id or name of a table or view 216 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listColumns 217 | * @return array 218 | */ 219 | public function listColumns($doc, $table, array $params = []) 220 | { 221 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/columns?'.http_build_query($params)); 222 | return $res; 223 | } 224 | /** 225 | * Returns a column in a table 226 | * 227 | * @param string $doc Id of a doc 228 | * @param string $table Id or name of a table 229 | * @param string $column Id or name of a column 230 | * @return array 231 | */ 232 | public function getColumn($doc, $table, $column) 233 | { 234 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/columns/'.$this->prepareStrings($column)); 235 | return $res; 236 | } 237 | /** 238 | * Returns rows in a table or view 239 | * 240 | * @param string $doc Id of a doc 241 | * @param string $table Id or name of a table or view 242 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listRows , useColumnNames is set true by default 243 | * @return array 244 | */ 245 | public function listRows($doc, $table, array $params = []) 246 | { 247 | $params['useColumnNames'] = $params['useColumnNames'] ?? true; 248 | if(isset($params['query'])) { 249 | $params['query'] = $this->array_key_first($params['query']).':"'.reset($params['query']).'"'; 250 | }; 251 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows?'.http_build_query($params)); 252 | return $res; 253 | } 254 | /** 255 | 256 | /** 257 | * Inserts or updates a row in a table 258 | * 259 | * @param string $doc Id of a doc 260 | * @param string $table Id or name of a table 261 | * @param array $rowData Associative array with your row data. Can be one row as array or an array of mulitple rows as an array (arrayception). Keys has to be column ids or names. 262 | * @param array $keyColumns Array with ids or names of columns. Coda will update rows instead of inserting, if keyColumns are matching 263 | * @param bool $disableParsing Disables automatic column format parsing. Default false. 264 | * @return bool 265 | */ 266 | public function insertRows($doc, $table, array $rowData, array $keyColumns = [], $disableParsing = false) 267 | { 268 | if($this->countDimension($rowData) == 1) 269 | $rowData = [$rowData]; 270 | $i = 0; 271 | foreach($rowData as $row) { 272 | foreach($row as $column => $value) { 273 | $params['rows'][$i]['cells'][] = ['column' => $column, 'value' => $value]; 274 | } 275 | $i++; 276 | } 277 | $params['keyColumns'] = $keyColumns; 278 | $query['disableParsing'] = $disableParsing; 279 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows', ['query' => $query, 'json' => $params], 'POST', true); 280 | if($res['statusCode'] === 202) { 281 | return true; 282 | } else { 283 | return $res; 284 | } 285 | } 286 | /** 287 | * Returns a row in a table 288 | * 289 | * @param string $doc Id of a doc 290 | * @param string $table Id or name of a table 291 | * @param string $row Id or name of a column 292 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/getRow , useColumnNames is set true by default 293 | * @return array 294 | */ 295 | public function getRow($doc, $table, $row, array $params = []) 296 | { 297 | $params['useColumnNames'] = $params['useColumnNames'] ?? true; 298 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows/'.$this->prepareStrings($row).'?'.http_build_query($params)); 299 | return $res; 300 | } 301 | /** 302 | * Updates a row in a table or view 303 | * 304 | * @param string $doc Id of a doc 305 | * @param string $table Id or name of a table or view 306 | * @param string $row Id or name of a row 307 | * @param array $rowData Associative array with your row data 308 | * @param bool $disableParsing Disables automatic column format parsing. Default false. 309 | * @return string Id of the updated row 310 | */ 311 | public function updateRow($doc, $table, $row, array $rowData, $disableParsing = false) 312 | { 313 | foreach($rowData as $column => $value) { 314 | $params['row']['cells'][] = ['column' => $column, 'value' => $value]; 315 | } 316 | $query['disableParsing'] = $disableParsing; 317 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows/'.$this->prepareStrings($row), ['query' => $query, 'json' => $params], 'PUT'); 318 | return $res; 319 | } 320 | /** 321 | * Deletes a row in a table or view 322 | * 323 | * @param string $doc Id of a doc 324 | * @param string $table Id or name of a table or view 325 | * @param string $row Id or name of a row 326 | * @return string Id of the deleted row 327 | */ 328 | public function deleteRow($doc, $table, $row) 329 | { 330 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows/'.$this->prepareStrings($row), [], 'DELETE'); 331 | return $res; 332 | } 333 | 334 | /** 335 | * Pushes a button in a table or view 336 | * 337 | * @param string $doc Id of a doc 338 | * @param string $table Id or name of a table or view 339 | * @param string $row Id or name of a row 340 | * @param string $column Id or name of a column 341 | * @return string Id of the deleted row 342 | */ 343 | public function pushButton($doc, $table, $row, $column) 344 | { 345 | $res = $this->request('/docs/'.$doc.'/tables/'.$this->prepareStrings($table).'/rows/'.$this->prepareStrings($row).'/buttons/'.$this->prepareStrings($column), [], 'POST'); 346 | return $res; 347 | } 348 | /** 349 | * Returns formulas in a doc 350 | * 351 | * @param string $doc Id of a doc 352 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listFormulas 353 | * @return array 354 | */ 355 | public function listFormulas($doc, array $params = []) 356 | { 357 | $res = $this->request('/docs/'.$doc.'/formulas?'.http_build_query($params)); 358 | return $res; 359 | } 360 | /** 361 | * Returns a formula in a doc 362 | * 363 | * @param string $doc Id of a doc 364 | * @param string $formula Id or name of a formula 365 | * @return array 366 | */ 367 | public function getFormula($doc, $formula) 368 | { 369 | $res = $this->request('/docs/'.$doc.'/formulas/'.$this->prepareStrings($formula)); 370 | return $res; 371 | } 372 | /** 373 | * Returns controls in a doc 374 | * 375 | * @param string $doc Id of a doc 376 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#operation/listControls 377 | * @return array 378 | */ 379 | public function listControls($doc, array $params = []) 380 | { 381 | $res = $this->request('/docs/'.$doc.'/controls?'.http_build_query($params)); 382 | return $res; 383 | } 384 | /** 385 | * Returns a control in a doc 386 | * 387 | * @param string $doc Id of a doc 388 | * @param string $control Id or name of a control 389 | * @return array 390 | */ 391 | public function getControl($doc, $control) 392 | { 393 | $res = $this->request('/docs/'.$doc.'/controls/'.$this->prepareStrings($control)); 394 | return $res; 395 | } 396 | /** 397 | * Returns mutation status of asynchronous mutation 398 | * 399 | * @param string $requestId Id of a request 400 | * @return array 401 | */ 402 | public function getMutationStatus($requestId) 403 | { 404 | $res = $this->request('/mutationStatus/'.$requestId, [], 'GET', false, false); 405 | return $res; 406 | } 407 | /** 408 | * Resolves a link 409 | * 410 | * @param string $url Url of a doc 411 | * @return array 412 | */ 413 | public function resolveLink($url) 414 | { 415 | $res = $this->request('/resolveBrowserLink?url='.$this->prepareStrings($url), [], 'GET', false, false); 416 | return $res; 417 | } 418 | /** 419 | * Returns ACL medadata 420 | * 421 | * @param string $doc Id of a doc 422 | * @return array 423 | */ 424 | public function getACLMeta($doc) 425 | { 426 | $res = $this->request('/docs/'.$doc.'/acl/metadata'); 427 | return $res; 428 | } 429 | /** 430 | * Returns a list of permissions 431 | * 432 | * @param string $doc Id of a doc 433 | * @return array 434 | */ 435 | public function listPermissions($doc) 436 | { 437 | $res = $this->request('/docs/'.$doc.'/acl/permissions', [], 'GET', false, false); 438 | return $res; 439 | } 440 | /** 441 | * Adds permissions to the doc 442 | * 443 | * @param string $doc Id of a doc 444 | * @param string $access type of access (readonly, write, comment, none) 445 | * @param string|array $principal metadata about a principal 446 | * @param bool $notify if true, sends notification email 447 | * @return array 448 | */ 449 | public function addPermission($doc, $access, $principal, $notify = false) 450 | { 451 | $params['access'] = $access; 452 | $params['principal'] = $principal; 453 | $params['suppressEmail'] = !$notify; 454 | $res = $this->request('/docs/'.$doc.'/acl/permissions', ['json' => $params], 'POST'); 455 | return $res; 456 | } 457 | /** 458 | * Deletes permissions to the doc 459 | * 460 | * @param string $doc Id of a doc 461 | * @param string $permissionId the id of the permission entry 462 | * @return array 463 | */ 464 | public function deletePermission($doc, $permissionId) 465 | { 466 | $res = $this->request('/docs/'.$doc.'/acl/permissions/'.$permissionId, [], 'DELETE'); 467 | return $res; 468 | } 469 | /** 470 | * Adds a user to the doc (shortcut for permissions methods) 471 | * 472 | * @param string $doc Id of a doc 473 | * @param string $email email address of a user 474 | * @param string $access type of access (readonly, write, comment, none) 475 | * @param bool $notify if true, sends notification email 476 | * @return mixed 477 | */ 478 | public function addUser($doc, $email, $access = "write", $notify = false) 479 | { 480 | $principal = [ 481 | 'type' => 'email', 482 | 'email' => $email 483 | ]; 484 | return $this->addPermission($doc, $access, $principal, $notify); 485 | } 486 | /** 487 | * Removes a user from the doc (shortcut for permissions methods) 488 | * 489 | * @param string $doc Id of a doc 490 | * @param string $email email address of a user 491 | * @return mixed 492 | */ 493 | public function deleteUser($doc, $email) 494 | { 495 | $permissions = $this->listPermissions($doc); 496 | foreach($permissions['items'] as $permission) { 497 | if($permission['principal']['email'] == $email) { 498 | $id = $permission['id']; 499 | } 500 | } 501 | if(isset($id)) { 502 | return $this->deletePermission($doc, $id); 503 | } else { 504 | return false; 505 | } 506 | } 507 | /** 508 | * Runs an automation of type "webhook invoked" in a doc 509 | * 510 | * @param string $doc Id of a doc 511 | * @param string $automation Id of the automation rule 512 | * @return mixed 513 | */ 514 | public function runAutomation($doc, $ruleId) 515 | { 516 | $res = $this->request('/docs/'.$doc.'/hooks/automation/'.$ruleId, [], 'POST'); 517 | return $res; 518 | } 519 | 520 | /** 521 | * Returns analytics data for available docs per day. 522 | * 523 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listDocAnalytics 524 | * @return array 525 | */ 526 | public function listDocAnalytics(array $params = []) 527 | { 528 | $res = $this->request('/analytics/docs?'.http_build_query($params)); 529 | return $res; 530 | } 531 | /** 532 | * Returns analytics data for a given doc within the day. This method will return a 401 if the given doc is not in a Team or Enterprise workspace. 533 | * 534 | * @param string $doc Id of a doc 535 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listPageAnalytics 536 | * @return array 537 | */ 538 | public function listPageAnalytics($doc, array $params = []) 539 | { 540 | $res = $this->request('/analytics/docs/'.$doc.'/pages?'.http_build_query($params)); 541 | return $res; 542 | } 543 | /** 544 | * Returns analytics data for Packs the user can edit.. 545 | * 546 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listPackAnalytics 547 | * @return array 548 | */ 549 | public function listPackAnalytics(array $params = []) 550 | { 551 | $res = $this->request('/analytics/packs?'.http_build_query($params)); 552 | return $res; 553 | } 554 | /** 555 | * Returns analytics data for Pack formulas. 556 | * 557 | * @param string $pack Id of a pack 558 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listPackFormulaAnalytics 559 | * @return array 560 | */ 561 | public function listPackFormulaAnalytics($pack, array $params = []) 562 | { 563 | $res = $this->request('/analytics/packs/'.$pack.'/formulas?'.http_build_query($params)); 564 | return $res; 565 | } 566 | /** 567 | * Returns summarized analytics data for available docs. 568 | * 569 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listDocAnalyticsSummary 570 | * @return array 571 | */ 572 | public function getDocAnalyticsSummary(array $params = []) 573 | { 574 | $res = $this->request('/analytics/docs/summary?'.http_build_query($params)); 575 | return $res; 576 | } 577 | /** 578 | * Returns summarized analytics data for Packs the user can edit. 579 | * 580 | * @param array $params Optional query parameters listed here https://coda.io/developers/apis/v1#tag/Analytics/operation/listPackAnalyticsSummary 581 | * @return array 582 | */ 583 | public function getPackAnalyticsSummary(array $params = []) 584 | { 585 | $res = $this->request('/analytics/packs/summary?'.http_build_query($params)); 586 | return $res; 587 | } 588 | /** 589 | * Returns days based on Pacific Standard Time when analytics were last updated. 590 | * 591 | * @return array 592 | */ 593 | public function getAnalyticsUpdatedDay() 594 | { 595 | $res = $this->request('/analytics/updated'); 596 | return $res; 597 | } 598 | 599 | /** 600 | * Cleares the cache folder 601 | * 602 | * @param 603 | */ 604 | public function clearCache() 605 | { 606 | array_map( 'unlink', array_filter((array) glob(self::CACHE_DIR.'*') ) ); 607 | } 608 | /** 609 | * Counts dimensions of an array 610 | * 611 | * @param array $array 612 | * @return int 613 | */ 614 | protected function countDimension($array) 615 | { 616 | if (is_array(reset($array))) { $return = $this->countDimension(reset($array)) + 1; } else { $return = 1; } 617 | return $return; 618 | } 619 | /** 620 | * Prepares strings to be used in url 621 | * 622 | * @param string $string 623 | * @return string 624 | */ 625 | protected function prepareStrings($string) { 626 | // urleconde converts space to + but Coda can only read space as space or as %20. A little workaround encodes the string and converts space to %20 instead of +. 627 | $parts = array_map('urlencode', explode(' ', $string)); 628 | return implode('%20', $parts); 629 | } 630 | /** 631 | * Gets the first key of an array. Standard function in PHP >= 7.3.0. 632 | * 633 | * @param array $array 634 | * @return mixed 635 | */ 636 | protected function array_key_first(array $array) 637 | { 638 | return $array ? array_keys($array)[0] : null; 639 | } 640 | } --------------------------------------------------------------------------------