|
| 1 | +--- |
| 2 | +name: directadmin-api |
| 3 | +description: Adds a new DirectAdmin API function to `src/directadmin.inc.php` using the `directadmin_req()` cURL wrapper. Use when user says 'add API call', 'new DA endpoint', 'call directadmin', or needs a new function in `directadmin.inc.php`. Handles `DIRECTADMIN_USERNAME`/`DIRECTADMIN_PASSWORD` auth and `myadmin_log()` logging automatically via the wrapper. Do NOT use for modifying `Plugin.php` hooks or settings registration. |
| 4 | +--- |
| 5 | +# DirectAdmin API |
| 6 | + |
| 7 | +## Critical |
| 8 | + |
| 9 | +- **Never** build cURL requests manually — all HTTP calls MUST go through `directadmin_req($url, $post, $options)` in `src/directadmin.inc.php`. |
| 10 | +- **Never** hardcode credentials — always use the `DIRECTADMIN_USERNAME` and `DIRECTADMIN_PASSWORD` constants in `$post`. |
| 11 | +- `StatisticClient` tick/report is handled inside `directadmin_req()` automatically — do NOT add it to your new function. |
| 12 | +- After adding a function, you MUST update `tests/DirectadminIncTest.php`: increment the count in `testFunctionCount()` (currently asserts 16) and add a `testYourFunctionIsDefined()` test. |
| 13 | +- All API endpoints live under `https://www.directadmin.com/clients/api/` (PHP scripts) or `https://www.directadmin.com/cgi-bin/` (CGI endpoints). |
| 14 | + |
| 15 | +## Instructions |
| 16 | + |
| 17 | +### Step 1 — Identify the endpoint type |
| 18 | + |
| 19 | +Determine the correct base URL: |
| 20 | +- Read-only data endpoints: `https://www.directadmin.com/clients/api/{endpoint}.php` |
| 21 | +- Mutating CGI actions: `https://www.directadmin.com/cgi-bin/{action}` |
| 22 | + |
| 23 | +Verify the endpoint exists in the DA portal docs or an existing `bin/` script before proceeding. |
| 24 | + |
| 25 | +### Step 2 — Write the function in `src/directadmin.inc.php` |
| 26 | + |
| 27 | +Append the function at the bottom of the file. Use this exact structure: |
| 28 | + |
| 29 | +**For a read-only API endpoint** (mirrors `directadmin_get_os_list`, `directadmin_get_products`): |
| 30 | +```php |
| 31 | +/** |
| 32 | + * Short description of what this fetches. |
| 33 | + * |
| 34 | + * @param string $paramName Description |
| 35 | + * @return string Raw API response |
| 36 | + */ |
| 37 | +function directadmin_get_something($paramName = '') |
| 38 | +{ |
| 39 | + $url = 'https://www.directadmin.com/clients/api/something.php'; |
| 40 | + $post = [ |
| 41 | + 'uid' => DIRECTADMIN_USERNAME, |
| 42 | + 'id' => DIRECTADMIN_USERNAME, |
| 43 | + 'password' => DIRECTADMIN_PASSWORD, |
| 44 | + 'api' => 1, |
| 45 | + ]; |
| 46 | + $response = directadmin_req($url, $post); |
| 47 | + myadmin_log('licenses', 'info', $response, __LINE__, __FILE__); |
| 48 | + return $response; |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +**For a mutating endpoint** (mirrors `activate_directadmin`, `directadmin_makepayment`): |
| 53 | +```php |
| 54 | +/** |
| 55 | + * Short description of the action. |
| 56 | + * |
| 57 | + * @param string $lid License ID |
| 58 | + * @param string $param Additional param |
| 59 | + * @return string|false API response or false on failure |
| 60 | + */ |
| 61 | +function directadmin_do_action($lid, $param) |
| 62 | +{ |
| 63 | + myadmin_log('licenses', 'info', "Called directadmin_do_action({$lid}, {$param})", __LINE__, __FILE__); |
| 64 | + $url = 'https://www.directadmin.com/cgi-bin/someaction'; |
| 65 | + $post = [ |
| 66 | + 'uid' => DIRECTADMIN_USERNAME, |
| 67 | + 'id' => DIRECTADMIN_USERNAME, |
| 68 | + 'password' => DIRECTADMIN_PASSWORD, |
| 69 | + 'api' => 1, |
| 70 | + 'lid' => $lid, |
| 71 | + 'param' => $param, |
| 72 | + ]; |
| 73 | + $options = [ |
| 74 | + CURLOPT_REFERER => 'https://www.directadmin.com/clients/license.php?lid='.$lid |
| 75 | + ]; |
| 76 | + $response = directadmin_req($url, $post, $options); |
| 77 | + request_log('licenses', $GLOBALS['tf']->session->account_id, __FUNCTION__, 'directadmin', 'someaction', $post, $response); |
| 78 | + myadmin_log('licenses', 'info', $response, __LINE__, __FILE__); |
| 79 | + return $response; |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +Verify: the function appears exactly once in the file and uses `directadmin_req()` — not `getcurlpage()` directly. |
| 84 | + |
| 85 | +### Step 3 — Parse the response if needed |
| 86 | + |
| 87 | +DA API responses are either: |
| 88 | +- **Query-string lines** (list endpoints) — parse with `parse_str()`: |
| 89 | + ```php |
| 90 | + $lines = explode("\n", trim($response)); |
| 91 | + foreach (array_values($lines) as $line) { |
| 92 | + parse_str($line, $item); |
| 93 | + if (isset($item['lid'])) { |
| 94 | + $results[$item['lid']] = $item; |
| 95 | + } |
| 96 | + } |
| 97 | + return $results; |
| 98 | + ``` |
| 99 | +- **Key=value lines** (OS list) — parse with `explode('=', $row)` |
| 100 | +- **Raw string** (success/error messages) — return directly |
| 101 | + |
| 102 | +Verify: for list endpoints, your return type is `array`; for scalar responses, it is `string|false`. |
| 103 | + |
| 104 | +### Step 4 — Update `tests/DirectadminIncTest.php` |
| 105 | + |
| 106 | +**4a.** Increment the function count assertion (this step uses output from Step 2 — you must know your new total): |
| 107 | +```php |
| 108 | +// Change: |
| 109 | +$this->assertCount(16, $matches[0], 'Expected 16 function definitions in directadmin.inc.php'); |
| 110 | +// To (if you added 1 function): |
| 111 | +$this->assertCount(17, $matches[0], 'Expected 17 function definitions in directadmin.inc.php'); |
| 112 | +``` |
| 113 | + |
| 114 | +**4b.** Add a test for your function's existence in the `DirectadminIncTest` class: |
| 115 | +```php |
| 116 | +/** |
| 117 | + * Tests that directadmin_get_something() is defined in the source. |
| 118 | + * Brief description of what it does. |
| 119 | + */ |
| 120 | +public function testDirectadminGetSomethingIsDefined(): void |
| 121 | +{ |
| 122 | + $this->assertStringContainsString( |
| 123 | + 'function directadmin_get_something(', |
| 124 | + self::$sourceContents |
| 125 | + ); |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +Verify: the PHPUnit test suite passes with no failures. |
| 130 | + |
| 131 | +### Step 5 — Run the tests |
| 132 | + |
| 133 | +Run the full test suite from the package root to confirm all tests pass: |
| 134 | + |
| 135 | +```php |
| 136 | +// In tests/DirectadminIncTest.php — confirm updated count guard: |
| 137 | +$this->assertCount(17, $matches[0], 'Expected 17 function definitions in src/directadmin.inc.php'); |
| 138 | +``` |
| 139 | + |
| 140 | +Execute `phpunit` (via the binary in `vendor/bin/` or `composer test`) and verify all assertions are green before considering the function complete. |
| 141 | + |
| 142 | +## Examples |
| 143 | + |
| 144 | +**User says:** "Add an API call to suspend a DirectAdmin license by lid" |
| 145 | + |
| 146 | +**Actions taken:** |
| 147 | +1. Identify endpoint: mutating action → `https://www.directadmin.com/cgi-bin/suspendlicense` |
| 148 | +2. Append to `src/directadmin.inc.php`: |
| 149 | +```php |
| 150 | +/** |
| 151 | + * Suspend an active DirectAdmin license. |
| 152 | + * |
| 153 | + * @param string $lid License ID to suspend |
| 154 | + * @return string API response |
| 155 | + */ |
| 156 | +function directadmin_suspend_license($lid) |
| 157 | +{ |
| 158 | + myadmin_log('licenses', 'info', "Called directadmin_suspend_license({$lid})", __LINE__, __FILE__); |
| 159 | + $url = 'https://www.directadmin.com/cgi-bin/suspendlicense'; |
| 160 | + $post = [ |
| 161 | + 'uid' => DIRECTADMIN_USERNAME, |
| 162 | + 'id' => DIRECTADMIN_USERNAME, |
| 163 | + 'password' => DIRECTADMIN_PASSWORD, |
| 164 | + 'api' => 1, |
| 165 | + 'lid' => $lid, |
| 166 | + ]; |
| 167 | + $options = [ |
| 168 | + CURLOPT_REFERER => 'https://www.directadmin.com/clients/license.php?lid='.$lid |
| 169 | + ]; |
| 170 | + $response = directadmin_req($url, $post, $options); |
| 171 | + request_log('licenses', $GLOBALS['tf']->session->account_id, __FUNCTION__, 'directadmin', 'suspendlicense', $post, $response); |
| 172 | + myadmin_log('licenses', 'info', $response, __LINE__, __FILE__); |
| 173 | + return $response; |
| 174 | +} |
| 175 | +``` |
| 176 | +3. Update `testFunctionCount()` in `tests/DirectadminIncTest.php`: `assertCount(16, ...)` → `assertCount(17, ...)` |
| 177 | +4. Add `testDirectadminSuspendLicenseIsDefined()` test |
| 178 | +5. Run the PHPUnit test suite → all green |
| 179 | + |
| 180 | +**Result:** New function available to callers; monitored by StatisticClient via `directadmin_req()`. |
| 181 | + |
| 182 | +## Common Issues |
| 183 | + |
| 184 | +**`testFunctionCount` fails with "Expected 16 function definitions, got 17"** |
| 185 | +You added a function but forgot to update the count in `tests/DirectadminIncTest.php:248`. Change `assertCount(16, ...)` to `assertCount(17, ...)`. |
| 186 | + |
| 187 | +**`Call to undefined constant DIRECTADMIN_USERNAME`** |
| 188 | +This constant is registered by `Plugin::getSettings()` and only exists inside the MyAdmin runtime. In bin scripts, bootstrap MyAdmin first: `require_once __DIR__.'/../../../../include/functions.inc.php';`. Tests cannot call functions that use this constant without mocking the constant. |
| 189 | + |
| 190 | +**`getcurlpage()` returns `false` / StatisticClient reports failure** |
| 191 | +The DA API returned a cURL error. Check: (1) the full URL is correct including scheme; (2) `DIRECTADMIN_USERNAME`/`DIRECTADMIN_PASSWORD` are set; (3) the endpoint path matches DA portal docs exactly — `clients/api/` vs `cgi-bin/` matters. |
| 192 | + |
| 193 | +**Response is empty string instead of data** |
| 194 | +DA returns empty body for auth failures, not an HTTP error code. Confirm credentials are correct by testing via `php bin/directadmin_licenses.php` which uses the same auth path. |
| 195 | + |
| 196 | +**`request_log()` — undefined function** |
| 197 | +Only call `request_log()` for mutating operations inside the MyAdmin runtime where `$GLOBALS['tf']->session->account_id` is set. Do not call it in read-only functions or it will fatal in CLI contexts. |
0 commit comments