/;
+ }
+
+}
diff --git a/integrations/digitalocean/.github/CODEOWNERS b/integrations/digitalocean/.github/CODEOWNERS
deleted file mode 100644
index c226b66c..00000000
--- a/integrations/digitalocean/.github/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-@marketplace-eng
diff --git a/integrations/digitalocean/.github/CONTRIBUTING.md b/integrations/digitalocean/.github/CONTRIBUTING.md
deleted file mode 100644
index 1962a365..00000000
--- a/integrations/digitalocean/.github/CONTRIBUTING.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Contributing
-
-We enthusiastically encourage contributions of all sorts to our repository, from correcting typos, to improving checks or adding new ones.
-
-### Reporting Issues
-
-This section guides you through submitting an issue for the Marketplace Partners. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
-
-When you are reporting an issue, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).
-
-> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
-
-#### Before Submitting An Issue
-* **Check the [current issues](https://github.com/digitalocean/marketplace-partners/issues)**.
-
-#### How Do I Submit A (Good) Issue?
-
-Issues are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information listed below.
-
-Explain the problem and include additional details to help maintainers reproduce the problem:
-
-* **Use a clear and descriptive title** for the issue to identify the problem.
-* **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**.
-* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
-* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
-* **Explain which behavior you expected to see instead and why.**
-* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
diff --git a/integrations/digitalocean/.gitignore b/integrations/digitalocean/.gitignore
deleted file mode 100644
index 6a557f52..00000000
--- a/integrations/digitalocean/.gitignore
+++ /dev/null
@@ -1,43 +0,0 @@
-# Compiled source #
-###################
-*.com
-*.class
-*.dll
-*.exe
-*.o
-*.so
-
-# Packages #
-############
-# it's better to unpack these files and commit the raw source
-# git has its own built in compression methods
-*.7z
-*.dmg
-*.gz
-*.iso
-*.jar
-*.rar
-*.tar
-*.zip
-
-# Logs and databases #
-######################
-*.log
-*.sql
-*.sqlite
-
-# OS generated files #
-######################
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-ehthumbs.db
-Thumbs.db
-
-# App generated files #
-######################
-.vscode
-
-.Rproj.user
diff --git a/integrations/whmcs/README.md b/integrations/whmcs/README.md
new file mode 100644
index 00000000..9cc49b08
--- /dev/null
+++ b/integrations/whmcs/README.md
@@ -0,0 +1,36 @@
+# OpenPanel WHMCS Module 😎
+WHMCS module for [OpenPanel](https://openpanel.com)
+
+
+## Requirements
+
+- Server with OpenPanel Enterprise license
+- WHMCS
+
+## Installation
+
+1. Login to SSH for WHMCS server
+2. Navigate to `path_to_whmcs/modules/servers`
+3. Run this command to create a new folder and in it download the module:
+ ```bash
+ git clone https://github.com/stefanpejcic/openpanel-whmcs-module.git openpanel
+ ```
+
+## Configuration
+
+How to setup WHMCS and OpenPanel: https://openpanel.com/docs/articles/extensions/openpanel-and-whmcs/
+
+
+## Update
+
+1. Login to SSH for WHMCS server
+2. Navigate to `path_to_whmcs/modules/servers/openpanel`
+3. Run this command to download newer files:
+ ```bash
+ git pull
+ ```
+
+## Bug Reports
+
+Report [new issue on github](https://github.com/stefanpejcic/openpanel-whmcs-module/issues/new/choose)
+
diff --git a/integrations/whmcs/openpanel.php b/integrations/whmcs/openpanel.php
new file mode 100644
index 00000000..841b71cf
--- /dev/null
+++ b/integrations/whmcs/openpanel.php
@@ -0,0 +1,805 @@
+ $authEndpoint,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => json_encode(array(
+ 'username' => $params["serverusername"],
+ 'password' => $params["serverpassword"]
+ )),
+ CURLOPT_HTTPHEADER => array(
+ "Content-Type: application/json"
+ ),
+ ));
+
+ // Execute cURL request to authenticate
+ $response = curl_exec($curl);
+
+ // Check for errors
+ if (curl_errno($curl)) {
+ $token = false;
+ $error = "cURL Error: " . curl_error($curl);
+ } else {
+ // Decode the response JSON to get the token
+ $responseData = json_decode($response, true);
+ $token = isset($responseData['access_token']) ? $responseData['access_token'] : false;
+ $error = $token ? null : "Token not found in response";
+ }
+
+ // Close cURL session
+ curl_close($curl);
+
+ return array($token, $error);
+}
+
+
+function apiRequest($endpoint, $token, $data = null, $method = 'POST') {
+ // Prepare cURL request
+ $curl = curl_init();
+
+ // Set default cURL options
+ $options = array(
+ CURLOPT_URL => $endpoint,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HTTPHEADER => array(
+ "Authorization: Bearer " . $token,
+ "Content-Type: application/json"
+ ),
+ );
+
+ // Handle different HTTP methods
+ switch ($method) {
+ case 'POST':
+ if ($data !== null) {
+ $options[CURLOPT_POST] = true;
+ $options[CURLOPT_POSTFIELDS] = json_encode($data);
+ }
+ break;
+
+ case 'GET':
+ $options[CURLOPT_CUSTOMREQUEST] = 'GET';
+ break;
+
+ case 'PUT':
+ $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
+ if ($data !== null) {
+ $options[CURLOPT_POSTFIELDS] = json_encode($data);
+ }
+ break;
+
+ case 'CONNECT':
+ $options[CURLOPT_CUSTOMREQUEST] = 'CONNECT';
+ if ($data !== null) {
+ $options[CURLOPT_POSTFIELDS] = json_encode($data);
+ }
+ break;
+
+ case 'PATCH':
+ $options[CURLOPT_CUSTOMREQUEST] = 'PATCH';
+ if ($data !== null) {
+ $options[CURLOPT_POSTFIELDS] = json_encode($data);
+ }
+ break;
+
+ case 'DELETE':
+ $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
+ if ($data !== null) {
+ $options[CURLOPT_POSTFIELDS] = json_encode($data);
+ }
+ break;
+
+ default:
+ // Handle unsupported methods
+ throw new InvalidArgumentException("Unsupported method: $method");
+ }
+
+ // Set the options for the cURL request
+ curl_setopt_array($curl, $options);
+
+ // Execute cURL request
+ $response = curl_exec($curl);
+
+ // Decode the response JSON
+ $responseData = json_decode($response, true);
+
+ // Close cURL session
+ curl_close($curl);
+
+ return $responseData;
+}
+
+
+
+
+
+
+
+############### USER ACTIONS ################
+# CREATE ACCOUNT
+function openpanel_CreateAccount($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return $error; // Return the error message as a plain string
+ }
+
+ try {
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $createUserEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users';
+ $packageId = $params['pid']; // Get the Product ID (Package ID)
+
+ // Query the database to get the package name
+ $result = select_query("tblproducts", "name", array("id" => $packageId));
+ $data = mysql_fetch_array($result);
+ $packageName = $data['name']; // This is the package name
+
+ // Prepare data for user creation
+ $userData = array(
+ 'username' => $params["username"],
+ 'password' => $params["password"],
+ 'email' => $params["clientsdetails"]["email"],
+ 'plan_name' => $packageName
+ );
+
+ // Make API request to create user
+ $response = apiRequest($createUserEndpoint, $jwtToken, $userData);
+
+ if (isset($response['success']) && $response['success'] === true) {
+ return 'success';
+ } else {
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
+ }
+
+ } catch (Exception $e) {
+ logModuleCall(
+ 'openpanel',
+ __FUNCTION__,
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ return $e->getMessage();
+ }
+}
+
+
+# TERMINATE ACCOUNT
+function openpanel_TerminateAccount($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return $error; // Return the error message as a plain string
+ }
+
+ try {
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $userEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Step 1: Unsuspend the account if it's suspended
+ try {
+ $unsuspendData = array('action' => 'unsuspend');
+ $unsuspendResponse = apiRequest($userEndpoint, $jwtToken, $unsuspendData, 'PATCH');
+
+
+ } catch (Exception $e) {
+ // If unsuspend fails, check if the account doesn't exist
+ $errorMessage = $e->getMessage();
+ if (strpos($errorMessage, 'not found') !== false || strpos($errorMessage, 'User') !== false) {
+ // Account does not exist, return an error message
+ return 'Error: Account "' . $params["username"] . '" does not exist and could not be deleted.';
+ } else {
+ return 'Failed to unsuspend account before termination: ' . $errorMessage;
+ }
+ }
+
+ // Step 2: Now attempt to delete the account
+ try {
+ $response = apiRequest($userEndpoint, $jwtToken, null, 'DELETE');
+
+
+ if (isset($response['success']) && $response['success'] === true) {
+ return 'success';
+ } else {
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred during termination.';
+ }
+
+ } catch (Exception $e) {
+ // Log the exception for the delete action
+ logModuleCall(
+ 'openpanel',
+ 'TerminateAccount - Delete Exception',
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ // Handle exception during the delete action
+ return 'Error during account termination: ' . $e->getMessage();
+ }
+
+ } catch (Exception $e) {
+ logModuleCall(
+ 'openpanel',
+ __FUNCTION__,
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ return $e->getMessage();
+ }
+}
+
+
+
+# CHANGE PASSWORD FOR ACCOUNT
+function openpanel_ChangePassword($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return $error; // Return the error message as a plain string
+ }
+
+ try {
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $changePasswordEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Prepare data for password change
+ $passwordData = array('password' => $params["password"]);
+
+ // Make API request to change password for user
+ $response = apiRequest($changePasswordEndpoint, $jwtToken, $passwordData, 'PATCH');
+
+ // Log the API request and response
+ logModuleCall(
+ 'openpanel',
+ 'ChangePassword',
+ $passwordData,
+ $response
+ );
+
+ // Check for success in the response
+ if (isset($response['success']) && $response['success'] === true) {
+ return 'success';
+ } else {
+ // Return the error message from the response or a default message
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred during password change.';
+ }
+
+ } catch (Exception $e) {
+ // Log the exception
+ logModuleCall(
+ 'openpanel',
+ 'ChangePassword Exception',
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ // Return the exception message
+ return 'Error: ' . $e->getMessage();
+ }
+}
+
+
+
+# SUSPEND ACCOUNT
+function openpanel_SuspendAccount($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ // If JWT token is not received, return error message
+ if (!$jwtToken) {
+ return json_encode(array("success" => false, "message" => $error));
+ }
+
+ try {
+ // Prepare the API endpoint for suspending the account
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $suspendAccountEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Prepare data for account suspension
+ $suspendData = array('action' => 'suspend');
+
+ // Make the API request to suspend the account
+ $response = apiRequest($suspendAccountEndpoint, $jwtToken, $suspendData, 'PATCH');
+
+ // Check the API response for success or failure
+ if (isset($response['success']) && $response['success'] === true) {
+ return 'success';
+ } else {
+ // Return the error message from the API response
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
+ }
+
+ } catch (Exception $e) {
+ // Log the exception details
+ logModuleCall(
+ 'openpanel',
+ 'SuspendAccount Exception',
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ // Return the exception message
+ return 'Error: ' . $e->getMessage();
+ }
+}
+
+
+
+
+
+
+# UNSUSPEND ACCOUNT
+function openpanel_UnsuspendAccount($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ // If JWT token is not received, return error message
+ if (!$jwtToken) {
+ return json_encode(array("success" => false, "message" => $error));
+ }
+
+ try {
+ // Prepare the API endpoint to unsuspend the account
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $unsuspendAccountEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Prepare data for account unsuspension (if any)
+ $unsuspendData = array('action' => 'unsuspend');
+
+ // Make the API request to unsuspend the account
+ $response = apiRequest($unsuspendAccountEndpoint, $jwtToken, $unsuspendData, 'PATCH');
+
+
+ // Check the API response for success or failure
+ if (isset($response['success']) && $response['success'] === true) {
+ return 'success';
+ } else {
+ // Return the error message from the API response
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
+ }
+
+ } catch (Exception $e) {
+ // Log the exception details
+ logModuleCall(
+ 'openpanel',
+ 'UnsuspendAccount Exception',
+ $params,
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+
+ // Return the exception message
+ return 'Error: ' . $e->getMessage();
+ }
+}
+
+
+
+
+
+
+# CHANGE PACKAGE (PLAN)
+function openpanel_ChangePackage($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ // Only log token issues as these are critical.
+ logModuleCall('openpanel', 'ChangePackage', $params, "Error fetching token: $error");
+ return $error;
+ }
+
+ try {
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $changePlanEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Fetch the stored plan ID
+ $storedPlanId = $params['configoption1'];
+
+ // Retrieve available plans from the server
+ $plans = getAvailablePlans($params);
+
+ if (is_string($plans)) {
+ logModuleCall('openpanel', 'ChangePackage', $params, "Error retrieving plans: $plans");
+ return "Error retrieving plans: $plans";
+ }
+
+ // Find the actual plan name using the stored plan ID
+ $planName = null;
+ foreach ($plans as $plan) {
+ if ($plan['id'] == $storedPlanId) {
+ $planName = $plan['name'];
+ break;
+ }
+ }
+
+ if (!$planName) {
+ logModuleCall('openpanel', 'ChangePackage', $params, "No matching plan name found for stored plan ID: $storedPlanId");
+ return "Error: No matching plan name found for stored plan ID: $storedPlanId";
+ }
+
+ // Prepare data for changing plan
+ $planData = array('plan_name' => $planName);
+
+ // Make API request to change plan
+ $response = apiRequest($changePlanEndpoint, $jwtToken, $planData, 'PUT');
+
+ // Log only on failure
+ if (!(isset($response['success']) && $response['success'] === true)) {
+ logModuleCall('openpanel', 'ChangePackage', array('command' => "opencli user-change_plan {$params['username']} $planName"), $response);
+ return isset($response['error']) ? $response['error'] : 'An unknown error occurred during package change.';
+ }
+
+ return 'success';
+
+ } catch (Exception $e) {
+ logModuleCall('openpanel', 'ChangePackage Exception', $params, $e->getMessage(), $e->getTraceAsString());
+ return 'Error: ' . $e->getMessage();
+ }
+}
+
+
+
+############### AUTOLOGIN LINKS ##############
+
+# LOGIN FOR USERS ON FRONT
+function openpanel_ClientArea($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return 'Error: ' . $error . '
';
+ }
+
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $getLoginLinkEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Prepare data for login link generation
+ $loginData = array();
+
+ // Make API request to get login link
+ $response = apiRequest($getLoginLinkEndpoint, $jwtToken, $loginData, 'CONNECT');
+
+ if (isset($response["link"])) {
+ $code = '';
+ $code .= '
+ Login to OpenPanel
+ ';
+ $code .= 'One-time login link has already been used, please refresh the page to login again.
';
+ } else {
+ $code = 'Error: Unable to generate login link for OpenPanel. Please try again later.
';
+ if (isset($response["message"])) {
+ $code .= 'Server Response: ' . htmlentities($response["message"]) . '
';
+ }
+ }
+
+ return $code;
+}
+
+
+
+# LOGIN FROM admin/configservers.php
+function openpanel_AdminLink($params) {
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $adminLoginEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/login';
+
+ $code = '';
+ return $code;
+}
+
+
+# LOGIN FOR ADMINS FROM BACKEND
+function openpanel_LoginLink($params) {
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return 'Error: ' . $error . '
';
+ }
+
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $getLoginLinkEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
+
+ // Prepare data for login link generation
+ $loginData = array();
+
+ // Make API request to get login link
+ $response = apiRequest($getLoginLinkEndpoint, $jwtToken, $loginData, 'CONNECT');
+
+ if (isset($response["link"])) {
+ $code = '';
+ $code .= '
+ Login to OpenPanel
+ ';
+ $code .= 'One-time login link has already been used, please refresh the page to login again.
';
+ } else {
+ // Log or print the response in case of error
+ $code = 'Error: Unable to generate the login link. Please try again later.
';
+ if (isset($response["message"])) {
+ $code .= 'Server Response: ' . htmlentities($response["message"]) . '
';
+ }
+ }
+
+ return $code;
+}
+
+function getAvailablePlans($params) {
+ // Use WHMCS server parameters for OpenPanel
+ $username = $params['serverusername']; // OpenPanel username
+ $password = $params['serverpassword']; // OpenPanel password
+ $hostname = $params['serverhostname']; // OpenPanel hostname
+
+ $apiProtocol = getApiProtocol($hostname);
+ $plansEndpoint = $apiProtocol . $hostname . ':2087/api/plans'; // Correct endpoint for OpenPanel API
+
+ // Get the JWT token using the server credentials
+ list($jwtToken, $error) = getAuthToken($params);
+ if (!$jwtToken) {
+ return "Error fetching token: $error"; // Return error if token cannot be fetched
+ }
+
+ // Prepare cURL request with Bearer token
+ $curl = curl_init();
+ curl_setopt_array($curl, array(
+ CURLOPT_URL => $plansEndpoint,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HTTPHEADER => array(
+ "Authorization: Bearer " . $jwtToken,
+ "Content-Type: application/json"
+ ),
+ CURLOPT_CUSTOMREQUEST => 'GET',
+ // Enable SSL verification for production
+ CURLOPT_SSL_VERIFYHOST => 2, // Verify the SSL certificate's host
+ CURLOPT_SSL_VERIFYPEER => true, // Verify the SSL certificate
+ ));
+
+ // Execute the request
+ $response = curl_exec($curl);
+
+ // Capture any errors
+ if (curl_errno($curl)) {
+ return "cURL Error: " . curl_error($curl); // Return cURL error
+ }
+
+ // Close cURL session
+ curl_close($curl);
+
+ // Decode the response
+ $plansResponse = json_decode($response, true);
+
+ // Check if the plans were retrieved
+ if (isset($plansResponse['plans']) && is_array($plansResponse['plans'])) {
+ return $plansResponse['plans']; // Return the plans array
+ } else {
+ return "Error fetching plans: " . json_encode($plansResponse); // Return error
+ }
+}
+
+function openpanel_ConfigOptions() {
+ // Get the product ID from the request, if available
+ $productId = isset($_REQUEST['id']) ? (int)$_REQUEST['id'] : 0;
+
+ if (!$productId) {
+ // If no product ID exists yet, prompt the user to save first
+ return array(
+ 'Note' => array(
+ 'Description' => 'Please save the product first to configure options.',
+ ),
+ );
+ }
+
+ // Fetch the server group assigned to this product
+ $result = select_query('tblproducts', 'servergroup', array('id' => $productId));
+ $data = mysql_fetch_array($result);
+ $serverGroupId = $data['servergroup'];
+
+ if (!$serverGroupId) {
+ // If no server group is selected yet, do not show the plans field
+ return array(
+ 'Note' => array(
+ 'Description' => 'Please assign a server group to this product to fetch available plans.',
+ ),
+ );
+ }
+
+ // Fetch servers in the selected server group
+ $serversResult = select_query('tblservers', '*', array('disabled' => 0));
+ $servers = array();
+ while ($serverData = mysql_fetch_array($serversResult)) {
+ // Check if the server belongs to the selected server group
+ $serverGroupRelResult = select_query('tblservergroupsrel', 'groupid', array('serverid' => $serverData['id']));
+ while ($groupRel = mysql_fetch_array($serverGroupRelResult)) {
+ if ($groupRel['groupid'] == $serverGroupId) {
+ $servers[] = $serverData;
+ break;
+ }
+ }
+ }
+
+ if (count($servers) == 0) {
+ // No servers found in the group, show a message and don't load the plans field
+ return array(
+ 'Note' => array(
+ 'Description' => 'No servers found in the assigned server group.',
+ ),
+ );
+ }
+
+ // Use the first server in the group
+ $server = $servers[0];
+ $params = array(
+ 'serverhostname' => $server['hostname'],
+ 'serverusername' => $server['username'],
+ 'serverpassword' => decrypt($server['password']),
+ );
+
+ // Fetch available plans from OpenPanel
+ $plans = getAvailablePlans($params);
+
+ // Handle errors in fetching plans
+ if (is_string($plans)) {
+ // Error message
+ return array(
+ 'Note' => array(
+ 'Description' => 'Error fetching plans: ' . $plans,
+ ),
+ );
+ }
+
+ // Populate plans in the dropdown
+ $planOptions = array();
+ if ($plans && is_array($plans)) {
+ foreach ($plans as $plan) {
+ $planOptions[$plan['id']] = $plan['name'];
+ }
+ }
+
+ return array(
+ 'Plan' => array(
+ 'Type' => 'dropdown',
+ 'Options' => $planOptions,
+ 'Description' => 'Select a plan from OpenPanel',
+ ),
+ );
+}
+
+############### MAINTENANCE ################
+
+
+# TODO: GET USAGE FOR USERS!!!!!!!!
+function openpanel_UsageUpdate($params) {
+
+ # resposne should be formated like this:
+ #{
+ # "disk_usage": "1024 MB",
+ # "disk_limit": "2048 MB",
+ # "bandwidth_usage": "512 MB",
+ # "bandwidth_limit": "1024 MB"
+ #}
+
+ $apiProtocol = getApiProtocol($params["serverhostname"]);
+ $authEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/';
+
+ // Authenticate and get JWT token
+ list($jwtToken, $error) = getAuthToken($params);
+
+ if (!$jwtToken) {
+ return json_encode(array(
+ "success" => false,
+ "message" => $error
+ ));
+ }
+
+ // Prepare API endpoint for getting usage
+ $getUsageEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/usage/';
+
+ // Prepare cURL request for getting usage
+ $curl = curl_init();
+ curl_setopt_array($curl, array(
+ CURLOPT_URL => $getUsageEndpoint,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CUSTOMREQUEST => 'PATCH',
+ CURLOPT_HTTPHEADER => array(
+ "Authorization: Bearer " . $jwtToken,
+ "Content-Type: application/json"
+ ),
+ ));
+
+ // Execute cURL request for getting usage
+ $response = curl_exec($curl);
+
+ // Check for errors
+ if (curl_errno($curl)) {
+ $result = json_encode(array(
+ "success" => false,
+ "message" => "cURL Error: " . curl_error($curl)
+ ));
+ } else {
+ // Decode the response JSON
+ $usageData = json_decode($response, true);
+
+ // Loop through results and update database
+ foreach ($usageData as $user => $values) {
+ update_query("tblhosting", array(
+ "diskusage" => $values['disk_usage'],
+ "disklimit" => $values['disk_limit'],
+ "lastupdate" => "now()"
+ ), array("server" => $params['serverid'], "username" => $user));
+ }
+
+ $result = json_encode(array(
+ "success" => true,
+ "message" => "Usage updated successfully"
+ ));
+ }
+
+ // Close cURL session
+ curl_close($curl);
+
+ return $result;
+}
+
+?>
\ No newline at end of file
diff --git a/integrations/whmcs/whmcs.json b/integrations/whmcs/whmcs.json
new file mode 100644
index 00000000..e5c388b5
--- /dev/null
+++ b/integrations/whmcs/whmcs.json
@@ -0,0 +1,22 @@
+{
+ "schema": "1.0",
+ "type": "whmcs-servers",
+ "name": "OpenPanel",
+ "license": "proprietary",
+ "category": "provisioning",
+ "description": {
+ "name": "OpenPanel",
+ "tagline": "A Linux hosting control panel based on Docker.",
+ "long": ""
+ },
+ "support": {
+ "homepage": "https://openpanel.co/",
+ "docs_url": "https://dev.openpanel.co/api"
+ },
+ "authors": [
+ {
+ "name": "OPENPANEL",
+ "homepage": "https:\/\/openpanel.co\/"
+ }
+ ]
+}