'Nextcloud Provisioning', 'APIVersion' => '1.2', 'DefaultNonSSLPort' => '80', 'DefaultSSLPort' => '443', 'RequiresServer' => true, 'ServiceSingleSignOnLabel' => 'Login to Nextcloud', 'AdminSingleSignOnLabel' => 'Login to Nextcloud Admin', 'ListAccountsUniqueIdentifierDisplayName' => 'Domain', 'ListAccountsUniqueIdentifierField' => 'domain', 'ListAccountsProductField' => 'configoption1', ); } /** * Define SeaFile product configuration options. * * @see https://developers.whmcs.com/provisioning-modules/config-options/ * * @return array */ function nextcloud_ConfigOptions() { $configarray = array( 'quota' => array( 'Type' => 'text', 'Description' => 'Basis User-Quota für dieses Produkt in GB', 'Default' => '10', 'Size' => '15', 'FriendlyName' => 'User Quota', ), ); return $configarray; } /** * Test connection to a Nextcloud server with the given server parameters. * * Allows an admin user to verify that an API connection can be * successfully made with the given configuration parameters for a * server. * * When defined in a module, a Test Connection button will appear * alongside the Server Type dropdown when adding or editing an * existing server. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return array */ function nextcloud_TestConnection($params) { error_log("Nextcloud TestConnection: " . $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport']); $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/capabilities"; $response = nextcloud_send($nextcloudURL,$params['serverusername'],$params['serverpassword'],"GET"); if ($response === false) { return array( 'success' => false, 'error' => 'Error: cannot communicate with server ' . $nextcloudURL, ); } if ($response->ocs->meta->status == 'ok') { return array( 'success' => true, 'error' => '', ); } else { if (isset($response->ocs->meta->message)) { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not login to ' . $nextcloudURL, $response ); } return array( 'success' => false, 'error' => 'Error: could not login to ' . $nextcloudURL, ); } } /** * Usage Update * * Important: Runs daily per server not per product * Run Manually: /admin/reports.php?report=disk_usage_summary&action=updatestats * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/usage-update/ */ function nextcloud_UsageUpdate($params) { $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/"; $nextcloudTestURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/capabilities"; $response = nextcloud_send($nextcloudTestURL, $params['serverusername'],$params['serverpassword'],"GET"); if ($response === false) { return "Cannot communicate with server: " . $nextcloudURL; } if ($response->ocs->meta->status !== 'ok') { if (isset($response->ocs->meta->message)) { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not login to ' . $nextcloudURL, $response ); } return 'Error: could not login to ' . $nextcloudURL; } $servicesObj = Capsule::table('tblhosting') ->select('*') ->where('server', '=', $params['serverid']) ->where('domainstatus', '=', 'Active') ->get(); foreach((array)$servicesObj as $serviceObj) { $service = get_object_vars($serviceObj[0]); $response = nextcloud_send($nextcloudURL . "/" . $service['username'], $params['serverusername'],$params['serverpassword'],"GET"); $freeMegaBytes = round($response->ocs->data->quota->free / 1048576,2); $usedMegaBytes = round($response->ocs->data->quota->used / 1048576,2); $quotaMegaBytes = round($response->ocs->data->quota->quota / 1048576,2); if ($response->ocs->meta->status == 'ok') { try { Capsule::table('tblhosting') ->where('id', '=', $service['id']) ->update( array( 'diskusage' => $usedMegaBytes, 'disklimit' => $quotaMegaBytes, 'lastupdate' => Capsule::raw('now()') ) ); } catch (Exception $e) { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could update usage information for ' . $service['username'], $response->ocs->meta->message ); } } else { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could update usage information for ' . $service['username'], $response->ocs->meta->message ); } } } /** * Client area output logic handling. * * This function is used to define module specific client area output. It should * return an array consisting of a template file and optional additional * template variables to make available to that template. * * The template file you return can be one of two types: * * * tabOverviewModuleOutputTemplate - The output of the template provided here * will be displayed as part of the default product/service client area * product overview page. * * * tabOverviewReplacementTemplate - Alternatively using this option allows you * to entirely take control of the product/service overview page within the * client area. * * Whichever option you choose, extra template variables are defined in the same * way. This demonstrates the use of the full replacement. * * Please Note: Using tabOverviewReplacementTemplate means you should display * the standard information such as pricing and billing details in your custom * template or they will not be visible to the end user. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return array */ function nextcloud_ClientArea($params) { switch ($params['serverport']) { case '80': case '443': $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname']; break; default: $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport']; break; }; $clientInfo['basequota'] = $params['configoption1'] ? $params['configoption1'] : 1; $clientInfo['addonquota'] = $params['configoptions']['addonQuota'] ? $params['configoptions']['addonQuota'] : 0; $clientInfo['userquota'] = $clientInfo['basequota'] + $clientInfo['addonquota']; $clientInfo['mailaddress'] = $params['username']; $clientInfo['webmailurl'] = $nextcloudURL; $clientInfo['zimbraserver'] = parse_url($clientInfo['webmailurl'], PHP_URL_HOST); $clientinfo['url'] = $nextcloudURL; $clientinfo['user'] = $user; $clientinfo['mobile1'] = $app; $clientinfo['mobile2'] = $google; $clientinfo['winclient'] = $winClient; $clientinfo['macclient'] = $macClient; $clientinfo['linClient'] = $linClient; $clientinfo['stitle'] = $params['model']['product']['name']; return array( 'tabOverviewReplacementTemplate' => 'clientarea', 'vars' => $clientInfo, ); } /** * Change the password for a SeaFile account. * * Called when a password change is requested. This can occur either due to a * client requesting it via the client area or an admin requesting it from the * admin side. * * This option is only available to client end users when the product is in an * active status. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_ChangePassword($params) { $checkPassword = nextcloudCheckPassword($params['password']); if ($checkPassword != null) { return $checkPassword; } $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/" . $params['username']; $post = array('key' => 'password', 'value' => $params["password"]); $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "PUT", $post); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok') { return 'success'; } else { if ($response->ocs->meta->statuscode == '107') { return 'Das Passwort entspricht nicht den Grundanforderungen von Nextcloud'; } else { return 'Das Passwort konnte nicht geändert werden. Der Fehlercode war: ' . $response->ocs->meta->statuscode; } } } /** * Set a new quota of a SeaFile account. * * Called to apply a quota change of the service. It * is called to provision upgrade or downgrade orders, as well as being * able to be invoked manually by an admin user. * * This same function is called for upgrades and downgrades of both * products and configurable options. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_ChangePackage($params) { $addonQuota = $params['configoptions']['addonQuota']; $quota = $params['configoption1'] ? $params['configoption1'] : 1; $accountQuota = ($quota + $addonQuota); $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/" . $params['username']; $post = array('key' => 'quota', 'value' => $accountQuota . "MB"); $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "PUT", $post); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok') { try { Capsule::table('tblhosting') ->where('id', '=', $params['serviceid']) ->update( array( 'disklimit' => $userAccount['quota_total'], ) ); } catch (Exception $e) { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not update quota in database', $e->getMessage() ); return 'Error: could not update quota in database'; } if(nextcloudUpdateQuota($params) != 'success') { return 'Error: could not update addonQuota in database'; }; return 'success'; } else { return 'Der Account konnte nicht modifiziert werden. Der Fehlercode war: ' . $response->ocs->meta->statuscode; } } /** * Provision a new instance of a NextCloud account. * * Attempt to provision a new NextCloud account. This is * called any time provisioning is requested inside of WHMCS. Depending upon the * configuration, this can be any of: * * When a new order is placed * * When an invoice for a new order is paid * * Upon manual request by an admin user * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_CreateAccount($params) { $userName = $params['customfields']['username']; $firstName = $params['customfields']['firstname']; $lastName = $params['customfields']['lastname']; $loginEMail = $params['customfields']['email']; $loginPassword = $params['customfields']['password']; $addonQuota = $params['configoptions']['addonQuota']; $baseQuota = $params['configoption1'] ? $params['configoption1'] : 1; $addonQuota = $params['configoptions']['addonQuota'] ? $params['configoptions']['addonQuota'] : 0; $newAddQuota = $params['configoptions']['newAddQuota'] ? $params['configoptions']['newAddQuota'] : 0; $accountQuota = $baseQuota + $addonQuota + $newAddQuota; $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users"; $post = array( 'userid' => $userName, 'password' => $loginPassword, 'email' => $loginEMail, 'displayName' => $firstName . " " . $lastName, 'quota' => $accountQuota . "GB" ); $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "POST", $post); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok' && $response->ocs->meta->statuscode == '100') { try { Capsule::table('tblhosting') ->where('id', '=', $params['serviceid']) ->update( array( 'username' => $userName, 'password' => $loginPassword, 'disklimit' => $accountQuota * 1024, 'diskusage' => 0, ) ); } catch (\Exception $e) { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could save username & password in database', $e->getMessage() ); return 'Error: could save username & password in database'; } if(nextcloudUpdateQuota($params) != 'success') { return 'Error: could not update addonQuota in database'; }; return 'success'; } else { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not create user ' . $userName, $response ); if ($response->ocs->meta->statuscode == '102') { return "Der Account konnte nicht erstellt werden. Es existiert bereits ein Account mit diesem Namen"; } elseif ($response->ocs->meta->statuscode == '107') { return "Beim Erstellen des Accounts ist ein Fehler aufgetreten, der Fehler war: " . $response->ocs->meta->message; } } } /** * Set a nextcloud account to status inactive. * * Called when a suspension is requested. This is invoked automatically by WHMCS * when a product becomes overdue on payment or can be called manually by admin * user. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_SuspendAccount($params) { $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/" . $params['username'] . "/disable"; $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "PUT", array() ); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok' && $response->ocs->meta->statuscode == '100') { return 'success'; } else { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not suspend account ' . $params['username'], $response ); return 'Der Account konnte nicht deaktiviert werden: ' . $params['username']; } } /** * Set a SeaFile account to status active. * * Called when an un-suspension is requested. This is invoked * automatically upon payment of an overdue invoice for a product, or * can be called manually by admin user. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_UnsuspendAccount($params) { $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/" . $params['username'] . "/enable"; $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "PUT", array() ); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok' && $response->ocs->meta->statuscode == '100') { return 'success'; } else { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not unsuspend account ' . $params['username'], $response ); return 'Der Account konnte nicht reaktiviert werden: ' . $params['username']; } } /** * Removes a Nextcloud account. * * Called when a termination is requested. This can be invoked automatically for * overdue products if enabled, or requested manually by an admin user. * * @param array $params common module parameters * * @see https://developers.whmcs.com/provisioning-modules/module-parameters/ * * @return string 'success' or an error message */ function nextcloud_TerminateAccount($params) { $nextcloudURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'] . "/ocs/v1.php/cloud/users/" . $params['username']; $response = nextcloud_send($nextcloudURL, $params["serverusername"], $params["serverpassword"], "DELETE", array() ); if ($response === false) { return "Der Server kann nicht erreicht werden"; } if ($response->ocs->meta->status == 'ok' && $response->ocs->meta->statuscode == '100') { return 'success'; } else { logModuleCall( 'nextcloud', __FUNCTION__, $params, 'Error: could not delete account ' . $params['username'], $response ); return 'Der Account konnte nicht gelöscht werden: ' . $params['username']; } } function nextcloud_send($href, $username, $password, $action, $post = array()) { $postdata = http_build_query($post); $ch = curl_init(); if (strtoupper($action) == 'POST') { curl_setopt($ch, CURLOPT_POST, true); if ($postdata) { curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); } } if (strtoupper($action) == 'PUT') { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); if ($postdata) { curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); } } if (strtoupper($action) == 'GET') { curl_setopt($ch, CURLOPT_HTTPGET, true); } if (strtoupper($action) == 'DELETE') { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); } curl_setopt($ch, CURLOPT_URL, $href); curl_setopt($ch, CURLOPT_USERPWD, $username . ":" . $password); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_HTTPHEADER, array("Accept: application/json","OCS-APIRequest: true",'content-type: application/x-www-form-urlencoded')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, 60); $result_json = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpcode >= 200 && $httpcode < 300) { $result_bom = nextcloud_remove_utf8bom($result_json); $result = json_decode($result_bom); return($result); } else { error_log("NextCloud Module nextcloud_send(): Error while executing " . $href . " the result code was: " . $httpcode); logModuleCall( 'nextcloud', __FUNCTION__, $result_json, 'nextcloud_send: Error: error in response', $response ); return false; } } function nextcloud_remove_utf8bom($text) { $bom = pack('H*', 'EFBBBF'); $text = preg_replace("/^$bom/", '', $text); return $text; } /** * server side password check * * recheck the client side password check * in case that the client side check has been disabled * * @param string $pwd password * * @return string missing features or null if the password matches our needs */ function nextcloudCheckPassword($pwd) { if (strlen($pwd) < 8) { return 'Das das Passwort ist zu kurz. Es werden mind. 8 Zeichen benötigt'; } if (!preg_match('#[0-9]+#', $pwd)) { return 'Das Passwort muss mindestens eine Zahl enthalten'; } if (!preg_match('#[A-Z]+#', $pwd)) { return 'Das Passwort muss mindestens einen Grossbuchstaben (A-Z) enthalten'; } if (!preg_match('#[a-z]+#', $pwd)) { return 'Das Passwort muss mindestens einen Kleinbuchstaben (a-z) enthalten'; } if (!preg_match('#[^\w]+#', $pwd)) { return 'Das Passwort muss mindestens ein Sonderzeichen (.,-:=) enthalten'; } return null; } /** * Perform an update of customfields to prevent downgrades. * * Called in changePackage or createAccount functions. * * @param array $params common module parameters * * @return *success* or an error */ function nextcloudUpdateQuota($params) { if(isset($params['configoptions']['addonQuota'])) { $addonQuota = $params['configoptions']['addonQuota'] ? $params['configoptions']['addonQuota'] : 0 ; $newAddQuota = $params['configoptions']['newAddQuota'] ? $params['configoptions']['newAddQuota'] : 0; $addonQuota = $addonQuota + $newAddQuota; $addonQuotaFieldIDObj = Capsule::table('tblproductconfigoptions') ->join('tblhostingconfigoptions', 'tblproductconfigoptions.id', '=', 'tblhostingconfigoptions.configid') ->where('tblhostingconfigoptions.relid', '=', $params['serviceid']) ->where('tblproductconfigoptions.optionname', 'like', 'addonQuota%') ->select('tblhostingconfigoptions.id', 'tblproductconfigoptions.qtymaximum') ->get(); if($addonQuota > $addonQuotaFieldIDObj[0]->qtymaximum) { logModuleCall( 'seafile', __FUNCTION__, $params, 'Info: someone is trying to exceed the maximum size', '' ); $addonQuota = $addonQuotaFieldIDObj[0]->qtymaximum; } try { $updateAddonQuota = Capsule::table('tblhostingconfigoptions') ->where('id', $addonQuotaFieldIDObj[0]->id) ->update( [ 'qty' => $addonQuota, ] ); } catch (\Exception $e) { logModuleCall( 'nextcloud', __FUNCTION__, $updateAddonQuota, 'Error: could not save addonOuota in database.', $e->getMessage() ); return 'Error: could not save addonOuota in database.'; } $newAddQuotaFieldIDObj = Capsule::table('tblproductconfigoptions') ->join('tblhostingconfigoptions', 'tblproductconfigoptions.id', '=', 'tblhostingconfigoptions.configid') ->where('tblhostingconfigoptions.relid', '=', $params['serviceid']) ->where('tblproductconfigoptions.optionname', 'like', 'newAddQuota%') ->select('tblhostingconfigoptions.id') ->get(); try { $updateNewAddQuota = Capsule::table('tblhostingconfigoptions') ->where('id', $newAddQuotaFieldIDObj[0]->id) ->update( [ 'qty' => '0', ] ); } catch (\Exception $e) { logModuleCall( 'nextcloud', __FUNCTION__, $updateNewAddQuota, 'Error: could not reset newAddOuota in database.', $e->getMessage() ); return 'Error: could not reset newAddOuota in database.'; } } return 'success'; } ?>