Преглед на файлове

start with a copy of seafile module

andre преди 3 години
ревизия
fb86cf0247
променени са 22 файла, в които са добавени 2052 реда и са изтрити 0 реда
  1. 83 0
      CWPAddressAvailable.php
  2. 334 0
      api/Sf/Admin.php
  3. 10 0
      api/config.php
  4. 169 0
      api/test.php
  5. 442 0
      clientarea.tpl
  6. 23 0
      composer.json
  7. 755 0
      cwp.php
  8. 15 0
      lang/arabic.php
  9. 16 0
      lang/czech.php
  10. 16 0
      lang/danish.php
  11. 15 0
      lang/dutch.php
  12. 12 0
      lang/english.php
  13. 15 0
      lang/french.php
  14. 11 0
      lang/german.php
  15. 16 0
      lang/italian.php
  16. 15 0
      lang/norwegian.php
  17. 16 0
      lang/portuguese-br.php
  18. 14 0
      lang/portuguese-pt.php
  19. 16 0
      lang/spanish.php
  20. 15 0
      lang/swedish.php
  21. 16 0
      lang/turkish.php
  22. 28 0
      whmcs.json

+ 83 - 0
CWPAddressAvailable.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * Helper script to check the availibility of a Seafile Account useable with ajax requests
+ * 
+ * @see https://www.seafile.com
+ * @copyright Copyright (c) Thurdata GmbH 2021
+ * @license GPL
+ * 
+ */
+$pos = strpos($_SERVER['HTTP_REFERER'],getenv('HTTP_HOST'));
+if($pos===false) {
+    die('Restricted access');
+}
+$productID = $_GET['pid'];
+$accountName = $_GET['username'];
+if(!filter_var($accountName, FILTER_VALIDATE_EMAIL)) {
+    echo "invalid";
+    exit;
+}
+
+error_log("Seafile Address Available: ". $productID . "/" . $accountName);
+/**
+ * Requires the whmcs init
+ * Requires this PHP api to make soap calls and parse responses
+ */
+require_once(__DIR__ . '/../../../init.php');
+require_once(__DIR__ . '/api/Sf/Admin.php');
+use WHMCS\Database\Capsule;
+$whmcs = App::self();
+$accessData = array('seafileURL' => '', 'adminUser' => '', 'adminPass' => '');
+$serverGroupIDObj = Capsule::table('tblproducts')
+	->select('servergroup')
+	->where('id', '=', $productID)
+	->get();
+$serverGroupID = $serverGroupIDObj[0]->servergroup;
+$serverIDObj = Capsule::table('tblservergroupsrel')
+    ->select('serverid')
+    ->where('groupid', '=', $serverGroupID)
+    ->get();
+$serverID = $serverIDObj[0]->serverid;
+$server = Capsule::table('tblservers')
+	->select('hostname', 'secure', 'port', 'username', 'password')
+	->where('id', '=', $serverID)
+	->where('active', '=', 1)
+	->get();
+
+$accessData['seafileServer'] = $server[0]->hostname;
+$accessData['prefix'] = $server[0]->secure ? 'https://' : 'http://';
+$accessData['port'] = $server[0]->port;
+$accessData['adminUser'] = $server[0]->username;
+$adminPassCrypt = $server[0]->password;
+$adminPassDecrypt = localAPI('DecryptPassword', array('password2' => $adminPassCrypt));
+if ($adminPassDecrypt['result'] == 'success') {
+        $accessData['adminPass'] = $adminPassDecrypt['password'];
+}
+
+if (empty($accessData['port']) || $accessData['port'] == "") {
+    $seafileURL = $accessData['prefix'] . $accessData['seafileServer'] . ":443";
+} else {
+    $seafileURL = $accessData['prefix'] . $accessData['seafileServer'] . ':' . $accessData['port'];
+}
+$seafileAPI = new Sf_Admin($seafileURL,$accessData['adminUser'],$accessData['adminPass']);
+$response = $seafileAPI->login();
+
+if (isset($response['error_msg'])) {
+    logModuleCall(
+        'seafile',
+        __FUNCTION__,
+        $params,
+        'Error: could not login to ' . $seafileURL,
+        $response
+    );
+    error_log("  --> ERROR " . print_r($response,true));
+    echo "error";
+    exit();
+} else {
+    $existingAccount = $seafileAPI->getAccount($accountName);
+    if (isset($existingAccount['error_msg'])) {
+        echo 'yes';
+    } else {
+        echo 'no';
+    }
+}

+ 334 - 0
api/Sf/Admin.php

@@ -0,0 +1,334 @@
+<?php
+/**
+ * Sf_Admin
+ *
+ * @author André Genrich <andre.genrich@thurdata.ch>
+ * @author Roland Käser <roland.keaser@thurdata.ch>
+ * @version 0.9
+ * @copyright Copyright (c) 2021, Thurdata
+ * @example ../test.php
+ */
+/**
+ * Sf_Admin class documentation
+ */
+/**
+ * Sf_Admin is a class which allows to manage CWP accounts via web-api/v2.1-admin
+ *
+ * You may create, modify, migrate, delete and get the attributes of a CWP account using this class
+ *
+ * For the usage examples of all class methods check the source code of test.php
+ */
+class Sf_Admin {
+    private $loginSuccess;
+    private $constructorSuccess;
+    private $sfURL;
+    private $sfConType;
+    private $sfPort;
+    private $sfSecure;
+    private $sfAdminName;
+    private $sfPassword;
+    protected $sfToken;
+	/**
+	 * Constructor
+         * @param string $CWPURL CWP URL (example: https://CWP.my.lan)
+	 * @param string $username admin/user account's e-mail
+	 * @param string $password admin/user account's password
+	 * @param string $secure optional false to force unsecure (default true)
+	 */
+    function __construct($CWPURL, $username, $password, $secure=true) {
+        if(!in_array('curl', get_loaded_extensions())) {
+            $this->constructorSuccess = false;
+            return array('error_msg' => 'Error: PHP curl extension not available');
+        }
+        if (empty($CWPURL) || empty($username) || empty($password)) {
+            $this->constructorSuccess = false;
+            return array('error_msg' => 'Error: Server login info missing, check server configuration');
+        }
+        if(preg_match('/^https/', $CWPURL)) {
+            $this->sfConType = 'https://';
+            if($secure) {
+                $this->sfSecure = true;
+            } else {
+                $this->sfSecure = false;
+            }
+        }else {
+            $this->sfConType = 'http://';
+            $this->sfSecure = false;
+        }
+        $sfHostname = str_replace(array('http://', 'https://'), array('',''), $CWPURL);
+        $sfHostname = explode(':', $sfHostname);
+        if (gethostbyname($sfHostname[0]) == $sfHostname[0] && !filter_var($sfHostname[0], FILTER_VALIDATE_IP)) {
+            $this->constructorSuccess = false;
+            return array('error_msg' => 'Error: Cannot resolve ' . $sfHostname[0] . ', check server configuration');
+        }
+        $this->sfPort = ($sfHostname[1]) ? $sfHostname[1] :  '8000';
+        $this->sfURL = $this->sfConType . $sfHostname[0] . ':' . $this->sfPort;
+        $this->sfAdminName = $username;
+        $this->sfPassword = $password;
+        $this->sfToken = null;
+        $this->constructorSuccess = true;
+    }
+
+    public function constructorSuccess() {
+        return $this->constructorSuccess;
+    }
+	/**
+	 * login
+	 * 
+	 * fetch and set CWP lofin token
+	 * @return array error or true
+	 */
+    public function login() {
+        if (!$this->constructorSuccess()) {
+                return array('error_msg' => 'Error: construct failed, something missing');
+        }
+        $sfClient = curl_init();
+        
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api2/auth-token/');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_POST, true);
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($sfClient, CURLOPT_POSTFIELDS, array('username' => $this->sfAdminName, 'password' => $this->sfPassword));
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HEADER, false);
+        $authResponse = curl_exec($sfClient);
+        $responseCode = curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE);
+        if($responseCode != 200) {
+            curl_close($sfClient);
+            unset($sfClient);
+            return array('error_msg' => $authResponse, 'response_code' => $responseCode);
+        };
+        curl_close($sfClient);
+        unset($sfClient);
+        
+        $authResponseData = json_decode($authResponse, true);
+        if (!$authResponseData['token']) {
+                $this->loginSuccess = false;
+                error_log(print_r($authResponseData,true));
+                return array('error_msg' => $authResponse);
+        } else {
+                $this->sfToken = $authResponseData['token'];
+                $this->loginSuccess = true;
+                return true;
+        }
+    }
+	/**
+	 * loginSuccess
+	 * 
+	 * @return bool
+	 */
+    public function loginSuccess() {
+        return $this->loginSuccess;
+    }
+	/**
+	 * getAllAccounts
+	 * 
+	 * @return array of CWP accounts array of informations or error message
+	 */
+    public function getAllAccounts() {
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api/v2.1/admin/users/');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+    }
+	/**
+	 * getAccount
+	 * 
+	 * @param string $email login e-mail
+	 * 
+	 * @return array of account informations or error message
+	 */
+    public function getAccount($email) {
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api/v2.1/admin/users/' . $email . '/');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+	}
+	/**
+	 * getAccount
+	 * 
+	 * @param array $params avvount informations, email required.
+     * 
+	 * @see https://download.CWP.com/published/web-api/v2.1-admin/accounts.md#user-content-Add%20User
+     * 
+	 * @return array of account informations or error message
+	 */
+    public function createAccount($params) {
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        if(!isset($params['email'])) {
+            return array('error_msg' => 'Error: missing parameter email');
+        }
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api/v2.1/admin/users/');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+		curl_setopt($sfClient, CURLOPT_POST, 1);
+		curl_setopt($sfClient, CURLOPT_POSTFIELDS, $params);
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+	}
+	/**
+	 * modifyAccount
+	 * 
+	 * @param array $params avvount informations, email required.
+     * 
+	 * @see https://download.CWP.com/published/web-api/v2.1-admin/accounts.md#user-content-Add%20User
+     * 
+	 * @return array of account informations or error message
+	 */
+    public function modifyAccount($params) {
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        if(!isset($params['email'])) {
+            return array('error_msg' => 'Error: missing parameter email');
+        }
+        $account = $params['email'];
+        unset($params['email']);
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api/v2.1/admin/users/' . $account . '/');
+		curl_setopt($sfClient, CURLOPT_CUSTOMREQUEST, 'PUT');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+		curl_setopt($sfClient, CURLOPT_POST, 1);
+		curl_setopt($sfClient, CURLOPT_POSTFIELDS, $params);
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+	}
+	/**
+	 * deleteAccount
+	 * 
+	 * @param string $email login e-mail
+	 * 
+	 * @return array success => true or error message
+	 */
+    public function deleteAccount($email)
+	{
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        if(!isset($email)) {
+            return array('error_msg' => 'Error: missing parameter email');
+        }
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api/v2.1/admin/users/' . $email . '/');
+		curl_setopt($sfClient, CURLOPT_CUSTOMREQUEST, 'DELETE');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+	}
+	/**
+	 * migrateAccount
+	 * 
+	 * @param string $from source account login e-mail
+	 * @param string $to destination account login e-mail (must exist)
+	 * 
+	 * @return array success => true or error message
+	 */
+    public function migrateAccount($from, $to)
+	{
+        if (!$this->loginSuccess()) {
+            return array('error_msg' => 'Error: not authenticated');
+        }
+        if(!isset($from)) {
+            return array('error_msg' => 'Error: missing parameter from');
+        }
+        if(!isset($to)) {
+            return array('error_msg' => 'Error: missing parameter to');
+        }
+        $postFields = array();
+        $postFields['op'] = 'migrate';
+        $postFields['to_user'] = $to;
+        $sfClient = curl_init();
+        curl_setopt($sfClient, CURLOPT_URL, $this->sfURL . '/api2/accounts/' . $from . '/');
+        curl_setopt($sfClient, CURLOPT_CONNECTTIMEOUT, '30');
+        curl_setopt($sfClient, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($sfClient, CURLOPT_RETURNTRANSFER, true);
+        if(!$this->sfSecure) {
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($sfClient, CURLOPT_SSL_VERIFYHOST, false);
+        }
+        curl_setopt($sfClient, CURLOPT_HTTPHEADER, array( 'Authorization: Token ' . $this->sfToken, 'Accept: application/json; charset=utf-8; indent=4'));
+        curl_setopt($sfClient, CURLOPT_POST, 1);
+		curl_setopt($sfClient, CURLOPT_POSTFIELDS, $postFields);
+        $response = curl_exec($sfClient);
+        if(curl_getinfo($sfClient, CURLINFO_RESPONSE_CODE) != 200) {
+            curl_close($sfClient);
+            return array('error_msg' => $response);
+        };
+        curl_close($sfClient);
+        return json_decode($response, true);
+	}
+}

+ 10 - 0
api/config.php

@@ -0,0 +1,10 @@
+<?php
+////////////
+// Config //
+////////////
+
+$CWPURL = 'https://files.thurdata.ch:443';
+$CWPAdminEmail = 'info@thurdata.ch';
+$CWPAdminPassword = '';
+
+?>

+ 169 - 0
api/test.php

@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * test.php
+ *
+ * contains examples about how to use 
+ * class methods for Sf_Admin
+ *
+ * @author André Genrich <andre.genrich@thurdata.ch>
+ * @version 1
+ * @copyright Copyright (c) Thurdata GmbH 2020
+ * @license GPL
+ * @name test.php
+ */
+
+/////////////
+// Require //
+/////////////
+
+require_once('config.php');
+require_once('Sf/Admin.php');
+
+//////////
+// Args //
+//////////
+
+if(PHP_SAPI != 'cli')
+	$args = $_GET;
+else
+	$args = parse_args($argv);
+
+if(isset($args['action']))
+{
+	$action = $args['action'];
+}
+else
+{
+	echo 'No action, exiting' . PHP_EOL;
+	exit (-1);
+}
+
+function parse_args($argv){
+	array_shift($argv);
+	$out = array();
+	foreach ($argv as $arg){
+		if (substr($arg,0,2) == '--'){
+			$eqPos = strpos($arg,'=');
+			if ($eqPos === false){
+				$key = substr($arg,2);
+				$out[$key] = isset($out[$key]) ? $out[$key] : true;
+			} else {
+				$key = substr($arg,2,$eqPos-2);
+				$out[$key] = substr($arg,$eqPos+1);
+			}
+		} else if (substr($arg,0,1) == '-'){
+			if (substr($arg,2,1) == '='){
+				$key = substr($arg,1,1);
+				$out[$key] = substr($arg,3);
+			} else {
+				$chars = str_split(substr($arg,1));
+				foreach ($chars as $char){
+					$key = $char;
+					$out[$key] = isset($out[$key]) ? $out[$key] : true;
+				}
+			}
+		} else {
+			$out[] = $arg;
+		}
+	}
+	return $out;
+}
+
+///////////
+// Login //
+///////////
+
+$sf = new Sf_Admin($seafileURL, $seafileAdminEmail, $seafileAdminPassword);
+$r = $sf->login();
+if(isset($r['error_msg'])) {
+	echo 'Error : cannot login to ' . $seafileURL . ' :-(' . PHP_EOL;
+	print_r($r);
+	exit();
+}
+
+/////////////
+// Actions //
+/////////////
+
+// nop
+if($action == 'nop')
+{
+    echo 'OK : login success, no further operation :-)' . PHP_EOL;
+    exit;
+}
+
+// Get All Accounts
+if($action == 'gaa')
+{
+    $r = $sf->getAllAccounts();
+	if(isset($r['error_msg'])) {
+        echo 'Error : could not fetch list of accounts for '. $seafileURL . ' :-(' . PHP_EOL;
+        print_r($r);
+	} else {
+        echo 'OK : got a list of '. $r['total_count'] . ' accounts for ' . $seafileURL . ' :-)' . PHP_EOL;
+    }
+}
+
+// Get Account Informations
+if($action == 'gai')
+{
+	$r = $sf->getAccount($args['account_name']);
+	if(isset($r['error_msg'])) {
+        echo 'Error : could not fetch information of '. $args['account_name'] . ' :-(' . PHP_EOL;
+        print_r($r);
+	} else {
+        echo 'OK : got the infos for account ' . $args['account_name'] . ' :-)' . PHP_EOL;
+    }
+}
+
+// Create Account
+if($action == 'ca')
+{
+	$attrs = array('email'=> $args['account_name'], 'password'=> $args['password']);
+	$r = $sf->createAccount($attrs);
+	if(isset($r['error_msg'])) {
+		echo 'Error : cannot create account ' . $args['account_name'] . ' :-(' . PHP_EOL;
+        print_r($r);
+	} else {
+		echo 'OK : account ' . $args['account_name'] . ' created :-)' . PHP_EOL;
+    }
+}
+
+// Modify Account
+if($action == 'moa')
+{
+    $attrs = array('email'=> $args['account_name'], 'quota_total' => $args['quota_total']);
+	$r = $sf->modifyAccount($attrs);
+	if(isset($r['error_msg'])) {
+		echo 'Error : cannot modify account ' . $args['account_name'] . ' :-(' . PHP_EOL;
+		print_r($r);
+	} else {
+		echo 'OK : modify account ' . $args['account_name'] . ' :-)' . PHP_EOL;
+		print_r($r);
+	}
+}
+
+// Migrate Account
+if($action == 'mia')
+{
+	$r = $sf->migrateAccount($args['account_name'], $args['new_account_name']);
+	if(isset($r['error_msg'])) {
+		echo 'Error : account ' . $args['account_name'] . ' not migrated to ' . $args['new_account_name'] . ' :-(' . PHP_EOL;
+		print_r($r);
+	} else {
+		echo 'OK : account ' . $args['account_name'] . ' migrated to ' . $args['new_account_name'] . ' :-)' . PHP_EOL;
+	}
+}
+
+// Delete Account
+if($action == 'da')
+{
+	$r = $sf->deleteAccount($args['account_name']);
+	if(isset($r['error_msg'])) {
+		echo 'Error : account ' . $args['account_name'] . ' not deleted :-(' . PHP_EOL;
+		print_r($r);
+	} else {
+		echo 'OK : account ' . $args['account_name'] . ' deleted :-)' . PHP_EOL;
+	}
+}

+ 442 - 0
clientarea.tpl

@@ -0,0 +1,442 @@
+{*
+ **********************************************************
+ * Developed by: Team Theme Metro
+ * Website: http://www.thememetro.com
+ * Customized ny thurdata
+ **********************************************************
+*}
+{if $modulechangepwresult}
+  {if $modulechangepwresult == "success"}
+    {include file="$template/includes/alert.tpl" type="success" msg=$modulechangepasswordmessage textcenter=true}
+  {elseif $modulechangepwresult == "error"}
+    {include file="$template/includes/alert.tpl" type="error" msg=$modulechangepasswordmessage|strip_tags textcenter=true}
+  {/if}
+{/if}
+
+{if $modulecustombuttonresult}
+  {if $modulecustombuttonresult == "success"}
+    {include file="$template/includes/alert.tpl" type="success" msg=$LANG.moduleactionsuccess textcenter=true idname="alertModuleCustomButtonSuccess"}
+  {else}
+    {include file="$template/includes/alert.tpl" type="error" msg=$LANG.moduleactionfailed|cat:' ':$modulecustombuttonresult textcenter=true idname="alertModuleCustomButtonFailed"}
+  {/if}
+{/if}
+{if $pendingcancellation}
+  {include file="$template/includes/alert.tpl" type="error" msg=$LANG.cancellationrequestedexplanation textcenter=true idname="alertPendingCancellation"}
+{/if}
+{if $unpaidInvoice}
+  <div class="alert alert-{if $unpaidInvoiceOverdue}danger{else}warning{/if}" id="alert{if $unpaidInvoiceOverdue}Overdue{else}Unpaid{/if}Invoice">
+    <div class="pull-right">
+      <a href="viewinvoice.php?id={$unpaidInvoice}" class="btn btn-xs btn-default">
+        {lang key='payInvoice'}
+      </a>
+    </div>
+    {$unpaidInvoiceMessage}
+  </div>
+{/if}
+<div class="tab-content margin-bottom">
+  <div class="tab-pane fade show active" id="tabOverview">
+    {if $tplOverviewTabOutput}
+			{$tplOverviewTabOutput}
+    {else}
+      <div class="section">
+        <div class="product-details">
+          <div class="row row-eq-height row-eq-height-sm">
+            <div class="col-md-6">
+              <div class="product-holder product-status-{$rawstatus|strtolower}" style="min-height: unset; height:210px">
+                <div class="product-content">
+                  <div class="product-image">
+                    <div class="feature-icon">
+                      <img src="/templates/croster/thurdata/productgroups/{$gid}.svg" class="img-fluid" style="height:100px;">
+                    </div>
+                  </div>
+                  <h4><small>SeaFile Account</small> - {$mailaddress}</h4>
+                  <div class="status-sticker-wrapper">
+                    <div class="status-sticker product-status-{$rawstatus|strtolower}">
+                      {$status}
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="col-md-6">
+              <div class="product-info" style="min-height: unset; height:210px;">
+                <table width="100%" border="0">
+            	    <tr>
+                    <td class="list-heading" style="font-size: 85%;">{$LANG.clientareahostingregdate}</td>
+                    <td class="list-text"  style="font-size: 85%;">{$regdate}</td>
+                  </tr>
+            	    {if $billingcycle ne {lang key='orderpaymenttermfreeaccount'}}
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;">{$LANG.firstpaymentamount}</td>
+                      <td class="list-text"  style="font-size: 85%;">{$firstpaymentamount}</td>
+                    </tr>
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;">{$LANG.recurringamount}</td>
+                      <td class="list-text"  style="font-size: 85%;">{$recurringamount}</td>
+                    </tr>
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;">{$LANG.orderbillingcycle}</td>
+                      <td class="list-text"  style="font-size: 85%;">{$billingcycle}</td>
+                    </tr>
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;">{$LANG.clientareahostingnextduedate}</td>
+                      <td class="list-text"  style="font-size: 85%;">{$nextduedate}</td>
+                    </tr>
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;">{$LANG.orderpaymentmethod}</td>
+                      <td class="list-text"  style="font-size: 85%;">{$paymentmethod}</td>
+                    </tr>
+                  {else}
+                    <tr>
+                      <td class="list-heading" style="font-size: 85%;"><strong>Trial Account</strong></td>
+                      {if $rawstatus eq "terminated"}
+                          <td class="list-text" style="font-size:85%;">{lang key='domainRenewal.expiredDaysAgo' days=((($smarty.now - ($regdate|@strtotime)) / 86400)|round) - 14}</td>
+                      {elseif 1123200 > ($smarty.now - ($regdate|@strtotime)) && ($smarty.now - ($regdate|@strtotime)) > 950400} {* wenn Heute - RegisterDatum < 13 Tage && > 11 Tage *}
+                          <td class="list-text" style="color:orange; font-size:85%">{lang key='domainRenewal.expiringIn' days=(14 - (($smarty.now - ($regdate|@strtotime)) / 86400)|round)}</td>
+                      {elseif ($smarty.now - ($regdate|@strtotime)) > 1123200} {* wenn Heute - RegisterDatum > 13 Tage *}
+                          <td class="list-text" style="color:red; font-size:85%;">{lang key='trialLastDay'}</td>
+                      {else}
+                          <td class="list-text" style="font-size: 85%;">{lang key='domainRenewal.expiringIn' days=(14 - (($smarty.now - ($regdate|@strtotime)) / 86400)|round)}</td>
+                      {/if}
+                    </tr>
+                    <tr>
+                      <td colspan="2"><br /><br /></td>
+                    </tr>
+                    <tr>
+                      <td></td>
+                      <td>
+                        {if $rawstatus eq "terminated"}
+                          <a href="/cart.php?gid={$gid}" class="btn btn-block btn-primary">{lang key='orderNow'}</a>
+                        {else}
+                          <a href="/upgrade.php?type=package&id={$id}" class="btn btn-block btn-primary">{lang key='upgradeNow'}</a>
+                        {/if}
+                      </td>
+                    </tr>
+                  {/if}
+            		</table> 
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      {*
+        {if $showcancelbutton || $packagesupgrade}
+          <div class="row">
+            {if $packagesupgrade}
+              <div class="col-xs-{if $showcancelbutton}6{else}12{/if}">
+                <a href="upgrade.php?type=package&amp;id={$id}" class="btn btn-block btn-primary">{$LANG.upgrade}</a>
+              </div>
+            {/if}
+            {if $showcancelbutton}
+              <div class="col-xs-{if $packagesupgrade}6{else}12{/if}">
+                <a href="clientarea.php?action=cancel&amp;id={$id}" class="btn btn-block btn-danger {if $pendingcancellation}disabled{/if}">{if $pendingcancellation}{$LANG.cancellationrequested}{else}{$LANG.clientareacancelrequestbutton}{/if}</a>
+              </div>
+            {/if}
+          </div>
+        {/if}
+      *}
+      {if $systemStatus == 'Active'}
+        {foreach $hookOutput as $output}
+          <div class="section">
+            <div>
+              {$output}
+            </div>
+            <div class=" clearfix"></div>
+          </div>
+        {/foreach}
+        {if $moduleclientarea || $configurableoptions || $customfields || $lastupdate}
+          <div class="section">
+            <div class="card panel panel-tabs">
+              <div class="card-header">
+                <ul class="nav nav-pills card-header-pills flex-column flex-md-row">
+                  {if $moduleclientarea}
+                    <li class="nav-item">
+                      <a href="#manage" data-toggle="tab" class="nav-link active"><i class="fas fa-globe fa-fw"></i> {lang key='manage'}</a>
+                    </li>
+                  {/if}
+                  {if $configurableoptions}
+                    <li class="nav-item">
+                      <a href="#configoptions" data-toggle="tab" class="nav-link{if !$moduleclientarea} active{/if}"><i class="fas fa-cubes fa-fw"></i>Details</a>
+                    </li>
+                  {/if}
+                  {if $metricStats}
+                    <li class="nav-item">
+                      <a href="#metrics" data-toggle="tab" class="nav-link{if !$moduleclientarea && !$configurableoptions} active{/if}"><i class="fas fa-chart-line fa-fw"></i> {lang key='metrics.title'}</a>
+                    </li>
+                  {/if}
+                  {if $customfields}
+                    <li class="nav-item">
+                      <a href="#additionalinfo" data-toggle="tab" class="nav-link{if !$moduleclientarea && !$metricStats && !$configurableoptions} active{/if}"><i class="fas fa-info fa-fw"></i>Informationen</a>
+                    </li>
+                  {/if}
+                  {if $lastupdate}
+                    <li class="nav-item">
+                      <a href="#resourceusage" data-toggle="tab" class="nav-link{if !$moduleclientarea && !$configurableoptions && !$customfields} active{/if}"><i class="fas fa-inbox fa-fw"></i> {lang key='resourceUsage'}</a>
+                    </li>
+                  {/if}
+                </ul>
+              </div>
+              <div class="card-body">
+                <div class="tab-content">
+                  {if $configurableoptions}
+                    <div class="tab-pane fade{if !$moduleclientarea} show active{/if}" role="tabpanel" id="configoptions">
+                      <table width="100%" cellspacing="0" cellpadding="0" class="frame">
+                        <tr>
+                          <td>
+                            <table width="100%" border="0" cellpadding="10" cellspacing="0" class="table table-striped table-framed">
+                              <tr>
+                                <td align='left'>Benutzername</td>
+                                <td align="left">{$mailaddress}</td>
+                              </tr>
+                              <tr>
+                                <td align='left'>Seafile Speicher inklusive</td>
+                                <td align="left">{$basequota} GB</td>
+                              </tr>
+                              <tr>
+                                <td align='left'>Seafile Speicher zugebucht</td>
+                                <td align="left">{$addonquota} GB</td>
+                              </tr>
+                              <tr>
+                                <td align='left'>Seafile Speicher gesamt</td>
+                                <td align="left">{$userquota} GB</td>
+                              </tr>
+                              {if $moduleParams.configoption3 == 'on'}
+                              <tr>
+                                <td align='left'>Seafile Speicher</td>
+                                <td align="left">{$userquota * 2} GB</a></td>
+                              </tr>
+                              {/if}
+                            </table>
+                          </td>
+                        </tr>
+                      </table>
+                    </div>
+                  {/if}
+                  {if $metricStats}
+                    <div class="tab-pane fade{if !$moduleclientarea && !$configurableoptions} show active{/if}" role="tabpanel" id="metrics">
+                      {include file="$template/clientareaproductusagebilling.tpl"}
+                    </div>
+                  {/if}
+                  {if $customfields}
+                    <div class="tab-pane fade{if !$moduleclientarea && !$configurableoptions && !$metricStats} show active{/if}" role="tabpanel" id="additionalinfo">
+                      <table width="100%" cellspacing="0" cellpadding="0" class="frame">
+                        <tr>
+                          <th width="50%"></th>
+                          <th width="50%"></th>
+                        </tr>
+                        {if $moduleParams.configoption3 == 'on'}
+                          <tr>
+                            <td colspan=2 align='center'>
+                              <br />
+                              <h6 class="heading">Seafile URL</h6>
+                            </td>
+                          </tr>
+                          <tr>
+                            <td colspan=2 align='center'>
+                              <h6 class="heading"><a href="{$moduleParams.configoption4}" target="_blank"> {$moduleParams.configoption4}</a></h6>
+                            </td>
+                          </tr>
+                        {else}
+                          <tr>
+                            <td colspan=2 align='center'>
+                              <br />
+                              <h6 class="heading">Seafile URL</h6>
+                            </td>
+                          </tr>
+                          <tr>
+                            <td colspan=2 align='center'>
+                              <h6 class="heading"><a href="{$url}" target="_blank">{$url}</a></h6>
+                            </td>
+                          </tr>
+                        {/if}
+                        <tr>
+                          <td colspan=2 align='center'>
+                            <br />
+                            <hr>
+                            <br />
+                          </td>
+                        <tr>
+                          <td colspan=2 align='center'>
+                            <h6 class="heading">Integrieren Sie Seafile auf Ihr Mobiltelefon oder Ihren PC</h6>
+                          </td>
+                        </tr>
+                        <tr>
+                          <td colspan=2 align='center'>
+                            <h6 class="heading">Als Benutzernamen verwenden Sie immer Ihren Seafile Account</h6>
+                          </td>
+                        </tr>
+                        <tr>
+                          <td colspan=2 align='center'>
+                            <br />
+                            <hr>
+                            <br />
+                          </td>
+                        </tr>
+                        <tr>
+                          <td align='center'>
+                            <strong>Windows Drive Client</strong><br />
+                            Zugriff auf die Daten über ein<br />
+                            eingebundenes Netzlaufwerk.<br />
+                            <br />
+                            <a href="{$drivewin}" target="_blank"><img src="modules/servers/seafile/seadrive-win.png" /></a>
+                          </td>
+                          <td align="center">
+                            <strong>Windows Client</strong><br />
+                            Synchronisierung Ihrer Dateien mit Seafile<br />
+                            Mehrere Konten auf veschiedenen Servern<br />
+                            <br />
+                            <a href="{$winclient}" target="_blank"><img src="modules/servers/seafile/windows.png" /></a>
+                          </td>
+                        </tr>
+                          <td colspan=2 align='left'>
+                            <br />
+                            <hr>
+                            <br />
+                          </td>
+                        </tr>
+                        <tr>
+                          <td align='center'>
+                            <strong>Apple Drive Client</strong><br />
+                            Zugriff auf Ihre Daten über eine im Finder
+                            direkt eingebundene Freigabe.
+                            Mehrere Konten auf veschiedenen Servern<br />
+                            <br />
+                            <a href="{$drivemac}" target="_blank"><img src="modules/servers/seafile/apple.png" /></a>
+                          </td>
+                          <td align="center">
+                            <strong>Apple Client</strong><br />
+                            Zugriff auf die Dateien via <br />
+                            Desktop Applikation<br />
+                            <br />
+                            <a href="{$macclient}" target="_blank"><img src="modules/servers/seafile/apple.png" /></a>
+                          </td>
+                        </tr>
+                          <td colspan=2 align='left'>
+                            <br />
+                            <hr>
+                            <br />
+                          </td>
+                        </tr>
+                        <tr>
+                          <td align='center'>
+                            <strong>App für Android</strong><br />
+                            Mit dem Seafile-Client für Android<br />
+                            können Sie Ihre Dateien über eine <br />
+                            verschlüsselte Verbindung völlig <br />
+                            sicher synchronisieren, bearbeiten und teilen.<br />
+                            <br />
+                            <a href="{$mobile2}" target="_blank"><img src="modules/servers/seafile/google_play.png" /></a>
+                          </td>
+                          <td align="center">
+                            <strong>App für iPhone</strong><br />
+                            Mit dem Seafile-Client für iPhone<br />
+                            können Sie Ihre Dateien über eine <br />
+                            verschlüsselte Verbindung völlig <br />
+                            sicher synchronisieren, bearbeiten und teilen.<br />
+                            <br />
+                          	<a href="{$mobile1}" target="_blank"><img src="modules/servers/seafile/apple_store.png" /></a>
+                          </td>
+                        </tr>
+                      </table>
+                    </div>
+                  {/if}
+                  {if $lastupdate}
+                    <div class="tab-pane fade" role="tabpanel" id="resourceusage" align="center">
+                      <div class="col-sm-10">
+                        <div class="col-sm-6">
+                          <h4>{lang key='diskSpace'}</h4>
+                          <input type="text" value="{$diskpercent|substr:0:-1}" class="dial-usage" data-width="100" data-height="100" data-min="0" data-readOnly="true" />
+                          <p>{($diskusage / 1024)|round:1} GB / {($disklimit / 1024)|round:1} GB</p>
+                        </div>
+                      </div>
+                      <div class="clearfix">
+                      </div>
+                      <p class="text-muted">{lang key='clientarealastupdated'}: {$lastupdate}</p>
+                      <script src="{$BASE_PATH_JS}/jquery.knob.js"></script>
+                      <script>
+                        jQuery(function() {
+                          jQuery(".dial-usage").knob({
+                            'format': function(v) {
+                              alert(v);
+                            }
+                          });
+                        });
+                      </script>
+                      <div class="col-xs-{if $showcancelbutton}6{else}12{/if}">
+                        {if $billingcycle != 'Gratis-Account'}
+                          <a href="upgrade.php?type=configoptions&amp;id={$id}" class="btn btn-block btn-primary">Weiteren Speicher hinzubuchen</a>
+                        {/if}
+                      </div>
+                    </div>
+                  {/if}
+                </div>
+              </div>
+            </div>
+          </div>
+        {/if}
+        {else}
+        <div class="alert-lg no-data">
+          <div class="icon">
+            <i class="fas fa-exclamation-triangle"></i>
+          </div>
+          <div class="text">
+            {if $suspendreason}
+              <strong>{$suspendreason}</strong><br />
+            {/if}
+            {$LANG.cPanel.packageNotActive} {$status}.<br />
+            {if $systemStatus eq "Pending"}
+              {$LANG.cPanel.statusPendingNotice}
+            {elseif $systemStatus eq "Suspended"}
+              {$LANG.cPanel.statusSuspendedNotice}
+            {/if}
+          </div>
+        </div>
+      {/if}
+    {/if}
+  </div>
+  <div class="tab-pane fade in" id="tabChangepw">
+    <div class="section">
+      <div class="section-header">
+        <h3>{$LANG.serverchangepassword}</h3>
+        <p class="desc">Hier können Sie Ihr Passwort für {$mailaddress} ändern</p>
+      </div>
+      <div class="section-body">
+        <div class="row">
+          <div class="col-sm-7">
+            <form class=" using-password-strength" method="post" action="{$smarty.server.PHP_SELF}?action=productdetails&id={$id}" role="form">
+              <input type="hidden" name="id" value="{$id}" />
+              <input type="hidden" name="modulechangepassword" value="true" />
+              <div class="TM-card">
+                <div id="newPassword1" class="form-group has-feedback">
+                  <label for="inputNewPassword1" class="control-label">{$LANG.newpassword}</label>
+                  <input type="password" class="form-control" id="inputNewPassword1" name="newpw" autocomplete="off" />
+                  <span class="form-control-feedback glyphicon"></span>
+                  {include file="$template/thurdata/thurpwcheck.tpl"}
+                </div>
+                <div class="alert alert-info">
+                  <div id='hints'>
+                    <strong id='hint2Head'></strong>
+                    <div id='hintLength'></div>
+                    <div id='hintNumeric'></div>
+                    <div id='hintSymbols'></div>
+                    <div id='hintUpperLower'></div>
+                  </div>
+                </div>
+                <div id="newPassword2" class="form-group has-feedback">
+                  <label for="inputNewPassword2" class="control-label">{$LANG.confirmnewpassword}</label>
+                  <input type="password" class="form-control" id="inputNewPassword2" name="confirmpw" autocomplete="off" />
+                  <span class="form-control-feedback glyphicon"></span>
+                  <div id="inputNewPassword2Msg">
+                  </div>
+                </div>
+              </div>
+              <div class="form-actions">
+                <input class="btn btn-primary" type="submit" value="{$LANG.clientareasavechanges}" />
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 23 - 0
composer.json

@@ -0,0 +1,23 @@
+{
+    "name": "Thurdata/Seafile",
+    "description": "Seafile Provisioing Module",
+    "version": "2.0.0",
+    "type": "project",
+    "license": "EULA",
+    "homepage": "http://www.thurdata.ch",
+    "support":
+            {
+                "email": "info@thurdata.ch",
+                "issues": "http://support.thurdata.ch",
+                "forum": "http://forum.thurdata.ch"
+            },
+    "authors": [
+	{
+	    "name": "Andre Genrich"
+	}
+    ],
+    "require":
+            {
+                "php": ">=7.1.0"
+            }
+}

+ 755 - 0
cwp.php

@@ -0,0 +1,755 @@
+<?php
+/**
+ * WHMCS Seafile Provisioning Module
+ *
+ * Provisioning for User Account on the Seafile Server
+ *
+ * @see https://www.seafile.com
+ * @copyright Copyright (c) Thurdata GmbH 2020
+ * @license GPL
+ */
+use WHMCS\Database\Capsule;
+
+require_once(__DIR__ . '/api/Sf/Admin.php');
+if (!defined('WHMCS')) {
+	die('This file cannot be accessed directly'); }
+
+
+function seafile_MetaData() {
+    return array(
+            'DisplayName' => 'Seafile Provisioning',
+            'APIVersion' => '1.2',
+            'DefaultNonSSLPort' => '8000',
+            'DefaultSSLPort' => '443',
+            'RequiresServer' => true,
+            'ServiceSingleSignOnLabel' => 'Login to Seafile',
+            'AdminSingleSignOnLabel' => 'Login to Seafile Admin',
+            'ListAccountsUniqueIdentifierDisplayName' => 'Domain',
+            'ListAccountsUniqueIdentifierField' => 'domain',
+            'ListAccountsProductField' => 'configoption1',
+    );
+}
+
+/**
+ * Define SeaFile product configuration options. 
+ *
+ * @see https://developers.whmcs.com/provisioning-modules/config-options/
+ *
+ * @return array
+ */
+function seafile_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 SeaFile 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 seafile_TestConnection($params) {
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	error_log("Seafile:TestConnection: " . $seafileURL);
+	//error_log("  -> " . $params['serverusername'] . "     " . $params['serverpassword']);
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+		return array(
+			'success' => false,
+			'error' => 'Error: could not login to ' . $seafileURL,
+		);
+	}
+	return array(
+		'success' => true,
+		'error' => '',
+	);
+}
+
+/**
+ * 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 seafile_UsageUpdate($params) {
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+    $servicesObj = Capsule::table('tblhosting')
+        ->select('*')
+        ->where('server', '=', $params['serverid'])
+        ->where('domainstatus', '=', 'Active')
+        ->get();
+    foreach((array)$servicesObj as $serviceObj) {
+		$service = get_object_vars($serviceObj[0]);
+		$accountInfo = $seafileAPI->getAccount($service['username']);
+		error_log("Seafile Module: Updating Disk Usage for: " . $accountInfo . " : " . round($accountInfo['quota_usage'] / 1000000,2) . "/" . round($accountInfo['quota_total'] / 1000000,2));
+		if(!isset($accountInfo['error_msg'])) {
+			try {
+				Capsule::table('tblhosting')
+					->where('id', '=', $service['id'])
+					->update(
+						array(
+							'diskusage' => round($accountInfo['quota_usage'] / 1000000,2),
+							'disklimit' => round($accountInfo['quota_total'] / 1000000,2),
+							'lastupdate' => Capsule::raw('now()')
+						)
+					);
+			} catch (Exception $e) {
+				logModuleCall(
+					'seafile',
+					__FUNCTION__,
+					$params,
+					'Error: could update usage information for ' . $service['username'],
+					$e->getMessage()
+				);
+			}
+		}
+    }
+}
+
+/**
+ * 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 seafile_ClientArea($params) {
+	switch ($params['serverport']) {
+		case '80':
+		case '443':
+			$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'];
+			break;
+		default:
+			$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+			break;
+	};
+	$app = 'https://itunes.apple.com/cn/app/seafile-pro/id639202512?l=en&mt=8';
+	$google = 'https://play.google.com/store/apps/details?id=com.seafile.seadroid2';
+	$driveWin = 'https://s3.eu-central-1.amazonaws.com/download.seadrive.org/seadrive-2.0.5-en.msi';
+	$winClient = 'https://s3.eu-central-1.amazonaws.com/download.seadrive.org/seafile-7.0.9-en.msi';
+	$macClient = 'https://s3.eu-central-1.amazonaws.com/download.seadrive.org/seafile-client-7.0.9.dmg';
+	$driveMac = 'https://s3.eu-central-1.amazonaws.com/download.seadrive.org/seadrive-2.0.5.dmg';
+	$linClient = 'https://download.seafile.com/published/seafile-user-manual/syncing_client/install_linux_client.md';
+	$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['stitle']           = $params['model']['product']['name'];
+    $langObj = Capsule::table('tblconfiguration')
+        ->select('value')
+        ->where('setting', '=', 'Language')
+        ->get();
+	$lla = $_SESSION['Language'] ? trim($_SESSION['Language']) : strtolower($langObj[0]->value);
+	$slang = array();
+	include_once dirname(__FILE__).'/lang/'.$lla.'.php';
+	return array(
+		'tabOverviewReplacementTemplate' => 'clientarea',
+		'vars' => array_merge(
+			array(
+				'url' => $seafileURL,
+				'mobile1' => $app,
+				'mobile2' => $google,
+				'drivewin' => $driveWin,
+				'winclient' => $winClient,
+				'macclient' => $macClient,
+				'drivemac' => $driveMac,
+				'linClient' => $linClient,
+			),
+			$slang,
+			$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 seafile_ChangePassword($params) { 
+	$checkPassword = seafileCheckPassword($params['password']);
+	if ($checkPassword != null) {
+	    return $checkPassword;
+	}
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$userAccount = $seafileAPI->getAccount($params['username']);
+	if(isset($userAccount['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not find account for: ' . $params['username'],
+			$userAccount
+		);
+		return 'Error: could not find account for: ' . $params['username'];
+	}
+	$result = $seafileAPI->modifyAccount(array('email' => $userAccount['email'], 'password' => $params['password']));
+	if(isset($result['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not change password for: ' . $params['username'],
+			$result
+		);
+		return 'Error: could not change password for: ' . $params['username'];
+	}
+	return 'success';
+}
+
+/**
+ * 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 seafile_ChangePackage($params) { 
+	$quota = $params['configoption1'] ? $params['configoption1'] : 1;
+	$addonQuota = $params['configoptions']['addonQuota'] ? $params['configoptions']['addonQuota'] : 0;
+    $newAddQuota = $params['configoptions']['newAddQuota'] ? $params['configoptions']['newAddQuota'] : 0;
+	$accountQuota = ($quota + $addonQuota + $newAddQuota);
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$userAccount = $seafileAPI->getAccount($params['username']);
+	if(isset($userAccount['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not find account ' . $params['username'],
+			$userAccount
+		);
+		return 'Error: could not find account ' . $params['username'];
+	}
+	$result = $seafileAPI->modifyAccount(array('email' => $userAccount['email'], 'quota_total' => $accountQuota * 1024));
+	if(isset($result['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not update quota for ' . $userAccount['email'],
+			$result
+		);
+		return 'Error: could not update quota for ' . $userAccount['email'];
+	} elseif ($result['quota_total'] != ($accountQuota * 1048576)) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: quota for ' . $userAccount['email'] . ' not updated',
+			$result
+		);
+		return 'Error: quota for ' . $userAccount['email'] . ' not updated';
+	}
+    try {
+        Capsule::table('tblhosting')
+            ->where('id', '=', $params['serviceid'])
+            ->update(
+                array(
+                    'disklimit' => $userAccount['quota_total'] / 1024,
+                )
+            );
+    } catch (Exception $e) {
+        logModuleCall(
+            'seafile',
+            __FUNCTION__,
+            $params,
+            'Error: could not update quota in database',
+            $e->getMessage()
+        );
+        return 'Error: could not update quota in database';
+    }
+    if(seafileUpdateQuota($params) != 'success') {
+        return 'Error: could not update addonQuota in database';
+    };
+    return 'success';
+}
+
+/**
+ * Provision a new instance of a SeaFile account.
+ *
+ * Attempt to provision a new SeaFile 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 seafile_CreateAccount($params) {
+	$firstName     = $params['customfields']['firstname'];
+	$lastName      = $params['customfields']['lastname'];
+	$loginEMail    = $params['customfields']['login'];
+	$loginPassword = $params['customfields']['password'];
+	$baseQuota     = $params['configoption1'];
+	$addonQuota    = $params['configoptions']['addonQuota'] ? $params['configoptions']['addonQuota'] : 0;
+	$newAddQuota   = $params['configoptions']['newAddQuota'] ? $params['configoptions']['newAddQuota'] : 0;
+	
+	//error_log( print_r($params,true)  );
+	
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$existingAccount = $seafileAPI->getAccount($loginEMail);
+	if(!isset($existingAccount['error_msg'])) {
+		return 'Error: account already exists ' . $loginEMail;
+	}
+	$accountQuota = ($baseQuota + $addonQuota + $newAddQuota) * 1024;
+	$newAccount = array();
+	$newAccount['email'] = $loginEMail;
+	$newAccount['name'] = $firstName . ' ' . $lastName;
+	$newAccount['isActive'] = 1;
+	$newAccount['isStaff'] = 0;
+	$newAccount['password'] = $loginPassword;
+	$newAccount['note'] = 'Account created from WHCMS for client ' . $params['userid'];
+	$newAccount['quota_total'] = $accountQuota;
+	error_log("Seafile:Create Account for " . $loginEMail . " with " . $accountQuota . "MB");
+	$result = $seafileAPI->createAccount($newAccount);
+	if(isset($result['error_msg'])) {
+	    logModuleCall(
+        	'seafile',
+        	__FUNCTION__,
+        	$params,
+    		'Error: could not create account ' . $loginEMail,
+    		$result
+		);
+	    return 'Error: could not create account ' . $loginEMail;
+	}
+	try {
+    	    Capsule::table('tblhosting')
+        	->where('id', '=', $params['serviceid'])
+        	->update(
+            	    array(
+                	'username'  => $loginEMail,
+                	'password'  => $params['customfields']['password'],
+                	'disklimit' => $accountQuota,
+					'diskusage' => 0,
+					'domain'	=> $loginEMail,
+            	    )
+        	);
+	} catch (\Exception $e) {
+    	    logModuleCall(
+        	'seafile',
+        	__FUNCTION__,
+        	$params,
+        	'Error: could save username & password in database',
+        	$e->getMessage()
+    	    );
+    	    return 'Error: could save username & password in database';
+	}
+	if(seafileUpdateQuota($params) != 'success') {
+    	    return 'Error: could not update addonQuota in database';
+	};
+	return 'success';
+}
+
+/**
+ * Set a SeaFile 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 seafile_SuspendAccount($params) {
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$userAccount = $seafileAPI->getAccount($params['username']);
+	if(isset($userAccount['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not find account ' . $params['username'],
+			$userAccount
+		);
+		return 'Error: could not find account ' . $params['username'];
+	}
+	$result = $seafileAPI->modifyAccount(array('email' => $userAccount['email'], 'is_active' => 0));
+	if(isset($result['error_msg'])) {
+	    logModuleCall(
+    		'seafile',
+    		__FUNCTION__,
+    		$params,
+    		'Error: could not suspend ' . $params['username'],    	    
+    		$result
+    	);
+	    return 'Error: could not suspend ' . $params['username'];
+	} elseif ($result['update_status_tip'] != 'Edit succeeded') {
+	    logModuleCall(
+    		'seafile',
+    		__FUNCTION__,
+    		$params,
+    		'Error: ' . $params['username'] . ' not deactivated',    	    
+    		$result
+    	);
+	    return 'Error: ' . $params['username'] . ' not deactivated';
+	}
+	return 'success';
+}
+
+/**
+ * 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 seafile_UnsuspendAccount($params) {
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$userAccount = $seafileAPI->getAccount($params['username']);
+	if(isset($userAccount['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not find account ' . $params['username'],
+			$userAccount
+		);
+		return 'Error: could not find account ' . $params['username'];
+	}
+	$result = $seafileAPI->modifyAccount(array('email' => $userAccount['email'], 'is_active' => 1));
+	if(isset($result['error_msg'])) {
+	    logModuleCall(
+    		'seafile',
+    		__FUNCTION__,
+    		$params,
+    		'Error: could not suspend ' . $params['username'],    	    
+    		$result
+    	);
+	    return 'Error: could not suspend ' . $params['username'];
+	} elseif ($result['update_status_tip'] != 'Edit succeeded') {
+	    logModuleCall(
+    		'seafile',
+    		__FUNCTION__,
+    		$params,
+    		'Error: ' . $params['username'] . ' not activated',    	    
+    		$result
+    	);
+	    return 'Error: ' . $params['username'] . ' not activated';
+	}
+	return 'success';
+}
+
+
+/**
+ * Removes a SeaFile 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 seafile_TerminateAccount($params) {
+	$seafileURL = $params['serverhttpprefix'] . '://' . $params['serverhostname'] . ':' . $params['serverport'];
+	$seafileAPI = new Sf_Admin($seafileURL,$params['serverusername'],$params['serverpassword']);
+	$response = $seafileAPI->login();
+	if (isset($response['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not login to ' . $seafileURL,
+			$response
+		);
+	    return 'Error: could not login to ' . $seafileURL;
+	}
+	$existingAccount = $seafileAPI->getAccount($params['username']);
+	if(isset($existingAccount['error_msg'])) {
+		logModuleCall(
+			'seafile',
+			__FUNCTION__,
+			$params,
+			'Error: could not find account ' . $params['username'],
+			''
+		);
+		return 'Error: could not find account ' . $params['username'];
+	}
+	//if ($existingAccount['is_active'] == 1) {
+	//	return 'Account '. $params['username'] . ' is active, suspend account first!';
+	//}
+	$result = $seafileAPI->deleteAccount($params['username']);
+	if($result != true) {
+	    logModuleCall(
+    		'seafile',
+    		__FUNCTION__,
+    		$params,
+    		'Error: could not remove ' . $params['username'],    	    
+    		$seafileAPI
+    	    );
+	    return 'Error: could not remove ' . $params['username'];
+	}
+	return 'success';
+}
+
+/**
+ * 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 seafileCheckPassword($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 seafileUpdateQuota($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(
+                'seafile',
+                __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(
+                'seafile',
+                __FUNCTION__,
+                $updateNewAddQuota,
+                'Error: could not reset newAddOuota in database.',
+                $e->getMessage()
+            );
+            return 'Error: could not reset newAddOuota in database.';
+        }
+    }
+    return 'success';
+}
+?>

+ 15 - 0
lang/arabic.php

@@ -0,0 +1,15 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'تسجيل الدخول ',
+
+'suser'		=> 'اسم المستخدم ',
+
+'sclients'  => 'تطبيق للحاسب / للمحمول',
+
+'surl'		=> 'الخادم',
+
+);
+
+?>

+ 16 - 0
lang/czech.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server přihlášení Info ',
+
+'suser'		=> 'Uživatelské jméno',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Server:',
+
+
+);
+
+?>

+ 16 - 0
lang/danish.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Log på Info ',
+
+'suser'		=> 'Brugernavn',
+
+'sclients'  => 'Desktop / Mobile App:',
+
+'surl'		=> 'Server:',
+
+
+);
+
+?>

+ 15 - 0
lang/dutch.php

@@ -0,0 +1,15 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Login Info',
+
+'suser'		=> 'Username',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Server:',
+
+);
+
+?>

+ 12 - 0
lang/english.php

@@ -0,0 +1,12 @@
+<?PHP
+
+$slang =  array(
+
+    'stitle'		=> 'Seafile Info',
+    'suser'		=> 'Username',
+    'spassword'		=> 'Reset Password',
+    'sclients'  	=> 'Desktop/Mobile App:',
+    'surl'		=> 'Server:',
+);
+
+?>

+ 15 - 0
lang/french.php

@@ -0,0 +1,15 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Connexion au serveur Infos',
+
+'suser'		=> "UsernameNom d'utilisateur",
+
+'sclients'  => 'App de bureau / mobile:',
+
+'surl'		=> 'Serveur:',
+
+);
+
+?>

+ 11 - 0
lang/german.php

@@ -0,0 +1,11 @@
+<?PHP
+
+$slang =  array(
+    'stitle'		=> 'Seafile Info',
+    'suser'		=> 'Benutzername ',
+    'spassword'		=> 'Passwort zurücksetzen ',
+    'sclients'  	=> 'Desktop/Mobile-App:',
+    'surl'		=> 'Server:',
+);
+
+?>

+ 16 - 0
lang/italian.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Login Info',
+
+'suser'		=> 'nome utente',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Server:',
+
+
+);
+
+?>

+ 15 - 0
lang/norwegian.php

@@ -0,0 +1,15 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Login Info',
+
+'suser'		=> 'Brukernavn',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Server:',
+
+);
+
+?>

+ 16 - 0
lang/portuguese-br.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Servidor Acesso Informações',
+
+'suser'		=> 'Nome de Usuário',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Servidor:',
+
+
+);
+
+?>

+ 14 - 0
lang/portuguese-pt.php

@@ -0,0 +1,14 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Servidor Acesso Informações',
+
+'suser'		=> 'Nome de Usuário',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Servidor:',
+);
+
+?>

+ 16 - 0
lang/spanish.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Servidor Entrar Info',
+
+'suser'		=> 'Nombre de usuario',
+
+'sclients'  => 'Escritorio/Mobile App:',
+
+'surl'		=> 'Servidor',
+
+
+);
+
+?>

+ 15 - 0
lang/swedish.php

@@ -0,0 +1,15 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Login Info',
+
+'suser'		=> 'Användarnamn ',
+
+'sclients'  => 'Desktop / app till mobilen:',
+
+'surl'		=> 'Server:',
+
+);
+
+?>

+ 16 - 0
lang/turkish.php

@@ -0,0 +1,16 @@
+<?PHP
+
+$slang =  array(
+
+'stitle'	=> 'Server Login Info',
+
+'suser'		=> 'Username',
+
+'sclients'  => 'Desktop/Mobile App:',
+
+'surl'		=> 'Server:',
+
+
+);
+
+?>

+ 28 - 0
whmcs.json

@@ -0,0 +1,28 @@
+{
+  "schema": "1.0",
+  "type": "whmcs-provisioning",
+  "name": "seafile",
+  "license": "GPL",
+  "category": "provisioning",
+  "description": {
+    "name": "Seafile",
+    "tagline": "Seafile Provisioning Module for WHMCS.",
+    "short": "Seafile users can be easily provoked with this module. The customer can also reset his Seafile password.",
+    "long": "The module allows single-user provisioning of Seafile accounts. It implements a simple seafile hosting for private customers with only one account each. The hosting of corporate environments was deliberately omitted, because they get their own instance (VM) for their seafile data."
+  },
+  "logo": {
+    "filename": "logo.png"
+  },
+  "support": {
+    "homepage": "https://www.thurdata.ch/whmcs",
+    "learn_more": "https://www.thurdata.ch/whmcs/#features",
+    "support_url": "https://www.thurdata.ch/support",
+    "docs_url": "https://www.thudata.ch/whmcs/docs"
+  },
+  "authors": [
+    {
+      "name": "Thurdata GmbH",
+      "homepage": "https://www.thurdata.ch/"
+    }
+  ]
+}