Browse Source

basic features working

andre 5 năm trước cách đây
mục cha
commit
1ac3aeaf98
6 tập tin đã thay đổi với 941 bổ sung0 xóa
  1. 1 0
      api
  2. 44 0
      templates/cfpasswordstrength.tpl
  3. 7 0
      templates/clientarea.tpl
  4. 368 0
      templates/configureproduct.tpl
  5. 391 0
      zimbraSingle.inc
  6. 130 0
      zimbraSingle.php

+ 1 - 0
api

@@ -0,0 +1 @@
+Subproject commit 0441c7bf07556b418f04e9aa3340495cd0b8bd62

+ 44 - 0
templates/cfpasswordstrength.tpl

@@ -0,0 +1,44 @@
+<script>
+jQuery(document).ready(function(){
+    $("#btnCompleteProductConfig").prop("disabled",true);
+    jQuery(":password").keyup(function () {
+        var pwStrengthErrorThreshold = 50;
+        var pwStrengthWarningThreshold = 75;
+
+        var $newPassword1 = jQuery(":password");
+        var pw = jQuery(":password").val();
+        var pwlength = (pw.length);
+        if (pwlength > 5) pwlength = 5;
+        var numnumeric = pw.replace(/[0-9]/g, "");
+        var numeric = (pw.length - numnumeric.length);
+        if (numeric > 3) numeric = 3;
+        var symbols = pw.replace(/\W/g, "");
+        var numsymbols = (pw.length - symbols.length);
+        if (numsymbols > 3) numsymbols = 3;
+        var numupper = pw.replace(/[A-Z]/g, "");
+        var upper = (pw.length - numupper.length);
+        if (upper > 3) upper = 3;
+        var pwstrength = ((pwlength * 10) - 20) + (numeric * 10) + (numsymbols * 15) + (upper * 10);
+        if (pwstrength < 0) pwstrength = 0;
+        if (pwstrength > 100) pwstrength = 100;
+
+        $newPassword1.removeClass('has-error has-warning has-success');
+        if (pwstrength < pwStrengthErrorThreshold) {
+            $newPassword1.addClass('has-error');
+            $newPassword1.css('background-color', '#f003');;
+        } else if (pwstrength < pwStrengthWarningThreshold) {
+            $newPassword1.addClass('has-warning');
+            $newPassword1.css('background-color', '#ff03');;
+        } else {
+            $newPassword1.addClass('has-success');
+            $newPassword1.css('background-color', '#0f03');;
+            $("#btnCompleteProductConfig").removeProp("disabled");
+        }
+    });
+});
+</script>
+{if file_exists("templates/$template/includes/alert.tpl")}
+    {include file="$template/includes/alert.tpl" type="info" msg="{$LANG.passwordtips}"}
+{elseif file_exists("templates/six/includes/alert.tpl")}
+    {include file="six/includes/alert.tpl" type="info" msg="{$LANG.passwordtips}"}
+{/if}

+ 7 - 0
templates/clientarea.tpl

@@ -0,0 +1,7 @@
+<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'>Webmail</td>
+            <td align="left"><a href="{$webmailURL}" target="_blank">{$webmailURL}</a></td>
+     </table>
+</td></tr></table>

+ 368 - 0
templates/configureproduct.tpl

@@ -0,0 +1,368 @@
+{include file="orderforms/{$carttpl}/common.tpl"}
+
+<script>
+var _localLang = {
+    'addToCart': '{$LANG.orderForm.addToCart|escape}',
+    'addedToCartRemove': '{$LANG.orderForm.addedToCartRemove|escape}'
+}
+</script>
+
+{if file_exists("templates/orderforms/{$carttpl}/layouts/head.tpl")}
+        {include file="templates/orderforms/{$carttpl}/layouts/head.tpl"}
+{/if}
+
+<div id="order-standard_cart">
+
+    <div class="row" style="position:relative;">
+
+        <div class="col-md-12">
+                        {if file_exists("templates/orderforms/{$carttpl}/layouts/head.tpl")}
+                        {else}
+            <div class="header-lined">
+                <h1>{$LANG.orderconfigure}</h1>
+            </div>
+                        {/if}
+
+                        {include file="orderforms/{$carttpl}/sidebar-categories-collapsed.tpl"}
+
+        </div>
+
+        <div class="col-md-12">
+
+            <form id="frmConfigureProduct">
+                <input type="hidden" name="configure" value="true" />
+                <input type="hidden" name="i" value="{$i}" />
+
+                <div class="row">
+                    <div class="col-md-9">
+
+                        <h3>{$LANG.orderForm.configureDesiredOptions}</h3>
+
+                        <div class="panel panel-default">
+                                                        <div class="panel-body">
+                                                                <p class="product-title"><strong><i>{$productinfo.group_name}</i> - {$productinfo.name}</strong></p>
+                                                                <p>{$productinfo.description}</p>
+                                                        </div>
+                        </div>
+
+                        <div class="alert alert-danger hidden" role="alert" id="containerProductValidationErrors">
+                            <p>{$LANG.orderForm.correctErrors}:</p>
+                            <ul id="containerProductValidationErrorsList"></ul>
+                        </div>
+
+                        {if $pricing.type eq "recurring"}
+                            <div class="field-container">
+                                <div class="form-group">
+                                    <label for="inputBillingcycle">{$LANG.cartchoosecycle}</label>
+                                    <select name="billingcycle" id="inputBillingcycle" class="form-control select-inline" onchange="{if $configurableoptions}updateConfigurableOptions({$i}, this.value);{else}recalctotals();{/if}">
+                                        {if $pricing.monthly}
+                                            <option value="monthly"{if $billingcycle eq "monthly"} selected{/if}>
+                                                {$pricing.monthly}
+                                            </option>
+                                        {/if}
+                                        {if $pricing.quarterly}
+                                            <option value="quarterly"{if $billingcycle eq "quarterly"} selected{/if}>
+                                                {$pricing.quarterly}
+                                            </option>
+                                        {/if}
+                                        {if $pricing.semiannually}
+                                            <option value="semiannually"{if $billingcycle eq "semiannually"} selected{/if}>
+                                                {$pricing.semiannually}
+                                            </option>
+                                        {/if}
+                                        {if $pricing.annually}
+                                            <option value="annually"{if $billingcycle eq "annually"} selected{/if}>
+                                                {$pricing.annually}
+                                            </option>
+                                        {/if}
+                                        {if $pricing.biennially}
+                                            <option value="biennially"{if $billingcycle eq "biennially"} selected{/if}>
+                                                {$pricing.biennially}
+                                            </option>
+                                        {/if}
+                                        {if $pricing.triennially}
+                                            <option value="triennially"{if $billingcycle eq "triennially"} selected{/if}>
+                                                {$pricing.triennially}
+                                            </option>
+                                        {/if}
+                                    </select>
+                                </div>
+                            </div>
+                        {/if}
+
+                        {if count($metrics) > 0}
+                            <div class="sub-heading">
+                                <span>{$LANG.metrics.title}</span>
+                            </div>
+
+                            <p>{$LANG.metrics.explanation}</p>
+
+                            <ul>
+                                {foreach $metrics as $metric}
+                                    <li>
+                                        {$metric.displayName}
+                                        -
+                                        {if count($metric.pricing) > 1}
+                                            {$LANG.metrics.startingFrom} {$metric.lowestPrice} / {if $metric.unitName}{$metric.unitName}{else}{$LANG.metrics.unit}{/if}
+                                            <button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target="#modalMetricPricing-{$metric.systemName}">
+                                                {$LANG.metrics.viewPricing}
+                                            </button>
+                                        {elseif count($metric.pricing) == 1}
+                                            {$metric.lowestPrice} / {if $metric.unitName}{$metric.unitName}{else}{$LANG.metrics.unit}{/if}
+                                            {if $metric.includedQuantity > 0} ({$metric.includedQuantity} {$LANG.metrics.includedNotCounted}){/if}
+                                        {/if}
+                                        {include file="$template/usagebillingpricing.tpl"}
+                                    </li>
+                                {/foreach}
+                            </ul>
+
+                            <br>
+                        {/if}
+
+                        {if $productinfo.type eq "server"}
+                            <div class="sub-heading">
+                                <span>{$LANG.cartconfigserver}</span>
+                            </div>
+
+                            <div class="field-container">
+
+                                <div class="row">
+                                    <div class="col-sm-6">
+                                        <div class="form-group">
+                                            <label for="inputHostname">{$LANG.serverhostname}</label>
+                                            <input type="text" name="hostname" class="form-control" id="inputHostname" value="{$server.hostname}" placeholder="servername.yourdomain.com">
+                                        </div>
+                                    </div>
+                                    <div class="col-sm-6">
+                                        <div class="form-group">
+                                            <label for="inputRootpw">{$LANG.serverrootpw}</label>
+                                            <input type="password" name="rootpw" class="form-control" id="inputRootpw" value="{$server.rootpw}">
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="row">
+                                    <div class="col-sm-6">
+                                        <div class="form-group">
+                                            <label for="inputNs1prefix">{$LANG.serverns1prefix}</label>
+                                            <input type="text" name="ns1prefix" class="form-control" id="inputNs1prefix" value="{$server.ns1prefix}" placeholder="ns1">
+                                        </div>
+                                    </div>
+                                    <div class="col-sm-6">
+                                        <div class="form-group">
+                                            <label for="inputNs2prefix">{$LANG.serverns2prefix}</label>
+                                            <input type="text" name="ns2prefix" class="form-control" id="inputNs2prefix" value="{$server.ns2prefix}" placeholder="ns2">
+                                        </div>
+                                    </div>
+                                </div>
+
+                            </div>
+                        {/if}
+
+                        {if $configurableoptions}
+                            <div class="sub-heading">
+                                <span>{$LANG.orderconfigpackage}</span>
+                            </div>
+                            <div class="product-configurable-options" id="productConfigurableOptions">
+                                <div class="row">
+                                    {foreach $configurableoptions as $num => $configoption}
+                                        {if $configoption.optiontype eq 1}
+                                            <div class="col-sm-12">
+                                                <div class="form-group">
+                                                    <label for="inputConfigOption{$configoption.id}">{$configoption.optionname}</label>
+                                                    <select name="configoption[{$configoption.id}]" id="inputConfigOption{$configoption.id}" class="form-control">
+                                                        {foreach key=num2 item=options from=$configoption.options}
+                                                            <option value="{$options.id}"{if $configoption.selectedvalue eq $options.id} selected="selected"{/if}>
+                                                                {$options.name}
+                                                            </option>
+                                                        {/foreach}
+                                                    </select>
+                                                </div>
+                                            </div>
+                                        {elseif $configoption.optiontype eq 2}
+                                            <div class="col-sm-12">
+                                                <div class="form-group">
+                                                    <label for="inputConfigOption{$configoption.id}">{$configoption.optionname}</label>
+                                                    {foreach key=num2 item=options from=$configoption.options}
+                                                        <br />
+                                                        <label>
+                                                            <input type="radio" name="configoption[{$configoption.id}]" value="{$options.id}"{if $configoption.selectedvalue eq $options.id} checked="checked"{/if} />
+                                                            {if $options.name}
+                                                                {$options.name}
+                                                            {else}
+                                                                {$LANG.enable}
+                                                            {/if}
+                                                        </label>
+                                                    {/foreach}
+                                                </div>
+                                            </div>
+                                        {elseif $configoption.optiontype eq 3}
+                                            <div class="col-sm-12">
+                                                <div class="form-group">
+                                                    <label for="inputConfigOption{$configoption.id}">{$configoption.optionname}</label>
+                                                    <br />
+                                                    <label>
+                                                        <input type="checkbox" name="configoption[{$configoption.id}]" id="inputConfigOption{$configoption.id}" value="1"{if $configoption.selectedqty} checked{/if} />
+                                                        {if $configoption.options.0.name}
+                                                            {$configoption.options.0.name}
+                                                        {else}
+                                                            {$LANG.enable}
+                                                        {/if}
+                                                    </label>
+                                                </div>
+                                            </div>
+                                        {elseif $configoption.optiontype eq 4}
+                                            <div class="col-sm-12">
+                                                <div class="form-group">
+                                                    <label for="inputConfigOption{$configoption.id}">{$configoption.optionname}</label>
+                                                    {if $configoption.qtymaximum}
+                                                        {if !$rangesliderincluded}
+                                                            <script type="text/javascript" src="{$BASE_PATH_JS}/ion.rangeSlider.min.js"></script>
+                                                            <link href="{$BASE_PATH_CSS}/ion.rangeSlider.css" rel="stylesheet">
+                                                            <link href="{$BASE_PATH_CSS}/ion.rangeSlider.skinModern.css" rel="stylesheet">
+                                                            {assign var='rangesliderincluded' value=true}
+                                                        {/if}
+                                                        <input type="text" name="configoption[{$configoption.id}]" value="{if $configoption.selectedqty}{$configoption.selectedqty}{else}{$configoption.qtyminimum}{/if}" id="inputConfigOption{$configoption.id}" class="form-control" />
+                                                        <script>
+                                                            var sliderTimeoutId = null;
+                                                            var sliderRangeDifference = {$configoption.qtymaximum} - {$configoption.qtyminimum};
+                                                            // The largest size that looks nice on most screens.
+                                                            var sliderStepThreshold = 25;
+                                                            // Check if there are too many to display individually.
+                                                            var setLargerMarkers = sliderRangeDifference > sliderStepThreshold;
+
+                                                            jQuery("#inputConfigOption{$configoption.id}").ionRangeSlider({
+                                                                min: {$configoption.qtyminimum},
+                                                                max: {$configoption.qtymaximum},
+                                                                grid: true,
+                                                                grid_snap: setLargerMarkers ? false : true,
+                                                                onChange: function() {
+                                                                    if (sliderTimeoutId) {
+                                                                        clearTimeout(sliderTimeoutId);
+                                                                    }
+
+                                                                    sliderTimeoutId = setTimeout(function() {
+                                                                        sliderTimeoutId = null;
+                                                                        recalctotals();
+                                                                    }, 250);
+                                                                }
+                                                            });
+                                                        </script>
+                                                    {else}
+                                                        <div>
+                                                            <input type="number" name="configoption[{$configoption.id}]" value="{if $configoption.selectedqty}{$configoption.selectedqty}{else}{$configoption.qtyminimum}{/if}" id="inputConfigOption{$configoption.id}" min="{$configoption.qtyminimum}" onchange="recalctotals()" onkeyup="recalctotals()" class="form-control form-control-qty" />
+                                                            <span class="form-control-static form-control-static-inline">
+                                                                x {$configoption.options.0.name}
+                                                            </span>
+                                                        </div>
+                                                    {/if}
+                                                </div>
+                                            </div>
+                                        {/if}
+                                        {if $num % 2 != 0}
+                                            </div>
+                                            <div class="row">
+                                        {/if}
+                                    {/foreach}
+                                </div>
+                            </div>
+
+                        {/if}
+
+                        {if $customfields}
+
+                            <div class="sub-heading">
+                                <span>{$LANG.orderadditionalrequiredinfo}</span>
+                            </div>
+
+                            <div class="field-container">
+                                {foreach $customfields as $customfield}
+                                    <div class="form-group">
+                                        <label for="customfield{$customfield.id}">{$customfield.name}</label>
+                                        {$customfield.input}
+                                        {if $customfield.description}
+                                            <span class="field-help-text">
+                                                {$customfield.description}
+                                            </span>
+                                        {/if}
+                                        {if $customfield.type == "password"}
+                                            {include file="$template/cfpwstrength.tpl"}
+                                        {/if}
+
+                                    </div>
+                                {/foreach}
+                            </div>
+
+                        {/if}
+
+                        {if $addons || count($addonsPromoOutput) > 0}
+
+                            <div class="sub-heading">
+                                <span>{$LANG.cartavailableaddons}</span>
+                            </div>
+
+                            {foreach $addonsPromoOutput as $output}
+                                <div>
+                                    {$output}
+                                </div>
+                            {/foreach}
+
+
+                            <div class="row addon-products">
+                                {foreach $addons as $addon}
+                                    <div class="col-sm-{if count($addons) > 1}6{else}12{/if}"">
+                                        <div class="panel panel-default panel-addon{if $addon.status} panel-addon-selected{/if}">
+                                            <div class="panel-body">
+                                               <input type="checkbox" name="addons[{$addon.id}]"{if $addon.status} checked{/if} />
+                                                <span class="panel-addon-name">{$addon.name}</span>
+                                                                                                <span class="panel-addon-price">{$addon.pricing}</span>
+                                                <p class="text-muted">{$addon.description}</p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                {/foreach}
+                            </div>
+
+                        {/if}
+
+                        <div class="alert alert-warning text-center">
+                            <i class="fas fa-question-circle"></i>
+                            {$LANG.orderForm.haveQuestionsContact} <a href="contact.php" target="_blank" class="alert-link">{$LANG.orderForm.haveQuestionsClickHere}</a>
+                        </div>
+
+                    </div>
+                    <div class="col-md-3" id="scrollingPanelContainer">
+
+                                                <div id="orderSummary">
+                            <div class="order-summary">
+                                <div class="loader" id="orderSummaryLoader">
+                                    <i class="fas fa-fw fa-sync fa-spin"></i>
+                                </div>
+                                <h2>{$LANG.ordersummary}</h2>
+                                <div class="summary-container" id="producttotal"></div>
+                            </div>
+                            <div class="text-center">
+                                 <button type="submit" id="btnCompleteProductConfig" class="btn btn-primary btn-lg">
+                                    {$LANG.continue}
+                                    <i class="fas fa-arrow-circle-right"></i>
+                                </button>
+                            </div>
+                        </div>
+
+                    </div>
+
+                </div>
+
+            </form>
+        </div>
+    </div>
+</div>
+
+{if file_exists("templates/orderforms/{$carttpl}/layouts/foot.tpl")}
+        {include file="templates/orderforms/{$carttpl}/layouts/foot.tpl"}
+{/if}
+
+
+<script language="javascript">
+        recalctotals();
+</script>

+ 391 - 0
zimbraSingle.inc

@@ -0,0 +1,391 @@
+<?php
+
+use WHMCS\Database\Capsule;
+require_once("api/Zm/Auth.php");
+require_once("api/Zm/Account.php");
+require_once("api/Zm/Domain.php");
+require_once("api/Zm/Server.php");
+
+function zimbraSingle_MetaData()
+{
+    return array(
+        'DisplayName' => 'Zimbra Single Mailbox Provisioning',
+        'APIVersion' => '1.2',
+        'DefaultNonSSLPort' => '7071',
+        'DefaultSSLPort' => '7071',
+        'RequiresServer' => true,
+        'ServiceSingleSignOnLabel' => 'Login to Zimbra',
+        'AdminSingleSignOnLabel' => 'Login to Zimbra Admin'
+    );
+}
+/**
+ */
+function zimbraSingleGetAccess()
+{
+    $accessData = array('zimbraServer' => '', 'adminUser' => '', 'adminPass' => '');
+    $servers = Capsule::table('tblservers')
+        ->select('ipaddress', 'username', 'password')
+        ->where('id', '=', $_SESSION[CreatedServerId])
+        ->get();
+    $accessData['zimbraServer'] = $servers[0]->ipaddress;
+    $accessData['adminUser'] = $servers[0]->username;
+    $adminPassCrypt = $servers[0]->password;
+    $adminPassDecrypt = localAPI('DecryptPassword', array('password2' => $adminPassCrypt));
+    if ($adminPassDecrypt['result'] == 'success') {
+        $accessData['adminPass'] = $adminPassDecrypt['password'];
+    }
+    return $accessData;
+}
+
+/**
+ * Checks if a given email address in the given domain already exists
+ *
+ * @param  $emailNameOnly       The name before the @-sign only
+ * @param  $domainName          The domain to search for existance of the email account
+ * @return true if such an account was found or false if not
+ */
+function zimbraSingleDoesEMailExist($emailNameOnly, $domainName)
+{
+    $account_name = $emailNameOnly . "@" . $domainName;
+    $accessData = zimbraSingleGetAccess();
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            "$login->getMessage()"
+        );
+        exit();
+    } else {
+        $apiAccountManager = new Zm_Account($api);
+            if( $apiAccountManager->accountExists($account_name)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
+
+/**
+*/
+function zimbraSingleCreateAccount($userData)
+{
+    $accessData = zimbraSingleGetAccess();
+    $attrs = array();
+    $attrs["gn"] = $userData["givenname"];
+    $attrs["sn"] = $userData["sn"];
+    $attrs["displayName"] = $attrs["gn"] . " " . $attrs["sn"];
+    $passDecrypt = localAPI('DecryptPassword', array('password2' => $userData['password']));
+    if ($passDecrypt['result'] == 'success') {
+        $userData['password'] = $passDecrypt['password'];
+    }
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    }
+    $apiAccountManager = new Zm_Account($api);
+    $cosName = $userData['cos'];
+    $cosID = $apiAccountManager->getCosId($cosName);
+    if(is_a($cosID, "Exception")) {
+        logModuleCall(
+        'zimbrasingle',
+        __FUNCTION__,
+        $params,
+        "Error : serviceclass $cosName not available",
+        ""
+        );
+        return false;
+    }
+    $attrs['zimbraCOSId'] = $cosID;
+    $id = $apiAccountManager->createAccount($account_name, $userData['password'], $attrs);
+    if(is_a($id, "Exception")) {
+        logModuleCall(
+        'zimbrasingle',
+        __FUNCTION__,
+        $params,
+        "Error : account $account_name not created",
+        ""
+        );
+        return false;
+    }
+    return $id;
+}
+
+function zimbraSingleSuspendAccount($userData)
+{
+    $accessData = zimbraSingleGetAccess();
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    } else {
+        $apiAccountManager = new Zm_Account($api);
+        $response = $apiAccountManager->setAccountStatus($account_name, "locked");
+        if(is_a($response, "Exception")) {
+            logModuleCall(
+                'zimbrasingle',
+                __FUNCTION__,
+                $params,
+                "Error : account $account_name could not locked",
+                ""
+            );
+            return false;
+        } else {
+            return $response;
+        }
+    }
+}
+
+function zimbraSingleUnsuspendAccount($userData)
+{
+    $accessData = zimbraSingleGetAccess();
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    } else {
+        $apiAccountManager = new Zm_Account($api);
+        $response = $apiAccountManager->setAccountStatus($account_name, "active");
+        if(is_a($response, "Exception")) {
+            logModuleCall(
+                'zimbrasingle',
+                __FUNCTION__,
+                $params,
+                "Error : account $account_name could not unlocked",
+                ""
+            );
+            return false;
+        } else {
+            return $response;
+        }
+    }
+}
+
+function zimbraSingleDeleteAccount($userData)
+{
+    $accessData = zimbraSingleGetAccess();
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    } else {
+        $apiAccountManager = new Zm_Account($api);
+        $response = $apiAccountManager->getAccountStatus($account_name);
+        if(is_a($response, "Exception")) {
+            logModuleCall(
+                'zimbrasingle',
+                __FUNCTION__,
+                $params,
+                "Error : account $account_name could not verified",
+                ""
+            );
+            return false;
+        }
+        if ($response != 'locked') {
+            return "Account $account_name active, suspend account first";
+        }
+        $response = $apiAccountManager->deleteAccount($account_name);
+        if(is_a($response, "Exception")) {
+            logModuleCall(
+                'zimbrasingle',
+                __FUNCTION__,
+                $params,
+                "Error : account $account_name could not removed",
+                ""
+            );
+            return false;
+        }
+        return 'success';
+    }
+}
+
+function zimbraSingleChangePassword($userData) {
+    $accessData = zimbraSingleGetAccess();
+    $passDecrypt = localAPI('DecryptPassword', array('password2' => $userData['password']));
+    if ($passDecrypt['result'] == 'success') {
+        $userData['password'] = $passDecrypt['password'];
+    }
+    if ($checkPW = zimbraSingleCheckPassword($userData['password'])) {
+        return $checkPW;
+    }
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    } else {
+        $apiAccountManager = new Zm_Account($api);
+        $response = $apiAccountManager->setAccountPassword($account_name, $userData['password']);
+        if(is_a($response, "Exception")) {
+            logModuleCall(
+                'zimbrasingle',
+                __FUNCTION__,
+                $params,
+                "Error : password for $account_name could not be set",
+                ""
+            );
+            return false;
+        } else {
+            return $response;
+        }
+    }
+}
+
+function zimbraSingleChangePackage($userData) {
+    $accessData = zimbraSingleGetAccess();
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    }
+    $apiAccountManager = new Zm_Account($api);
+    $response = $apiAccountManager->setAccountCos($account_name, $userData['cos']);
+    if(is_a($response, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : class of service for $account_name could not be set",
+            ""
+        );
+        return false;
+    }
+    return $response;
+}
+
+function zimbraSingleClientArea($userData)
+{
+    $accessData = zimbraSingleGetAccess();
+    $account_name = $userData['username'] . '@' . $userData['maildomain'];
+
+    $api = new Zm_Auth($accessData['zimbraServer'], $accessData['adminUser'], $accessData['adminPass'], "admin");
+    $login = $api->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : cannot login to " . $accessData['zimbraServer'],
+            ""
+        );
+        return false;
+    }
+    $apiAccountManager = new Zm_Account($api);
+    $response = $apiAccountManager->getAccountInfo($account_name);
+    if(is_a($response, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Error : could not gather informations for $account_name",
+            ""
+        );
+        return false;
+    }
+    $webMailURL = recursiveFindAll( $response, 'PUBLICMAILURL');
+    return $webMailURL[0]['DATA'];
+}
+
+function zimbraSingleCheckPassword($pwd)
+{
+    $message = '';
+    if (strlen($pwd) < 9) {
+        $message .= "Das das Passwort ist zu kurz. Es werden mind. 9 Zeichen benötigt<br>";
+    }
+
+    if (!preg_match("#[0-9]+#", $pwd)) {
+        $message .= "Das Passwort muss mindestens eine Zahl enthalten<br>";
+    }
+
+    if (!preg_match("#[A-Z]+#", $pwd)) {
+        $message .= "Das Passwort muss mindestens einen Grossbuchstaben (A-Z) enthalten<br>";
+    }
+
+    if (!preg_match("#[a-z]+#", $pwd)) {
+        $message .= "Das Passwort muss mindestens einen Kleinbuchstaben (a-z) enthalten<br>";
+    }
+
+    if (!preg_match("#[^\w]+#", $pwd)) {
+        $message .= "Das Passwort muss mindestens ein Sonderzeichen (.,-:=) enthalten<br>";
+    }
+    return $message;
+}
+
+function recursiveFindAll($haystack, $needle)
+{
+    $values = array();
+    $iterator  = new RecursiveArrayIterator($haystack);
+    $recursive = new RecursiveIteratorIterator(
+        $iterator,
+        RecursiveIteratorIterator::SELF_FIRST
+    );
+    foreach ($recursive as $key => $value) {
+        if ($key === $needle) {
+            array_push($values, $value);
+        }
+    }
+    return $values;
+}
+
+function zimbraSingleTestFunction()
+{
+    return 'blubb';
+}

+ 130 - 0
zimbraSingle.php

@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * WHMCS Zimbra Provisioning Module
+ *
+ * Provisioning for private user accounts on the Zimbra Server
+ *
+ * @see https://www.zimbra.com
+ * @copyright Copyright (c) Thurdata GmbH 2020
+ * @license GPL
+ */
+
+if (!defined("WHMCS")) {
+    die("This file cannot be accessed directly");
+}
+require_once dirname(__FILE__) . '/zimbraSingle.inc';
+
+function zimbraSingle_TestConnection($params)
+{
+    $auth = new Zm_Auth($params['serverip'], $params['serverusername'], $params['serverpassword'], "admin");
+    $login = $auth->login();
+    if(is_a($login, "Exception")) {
+        logModuleCall(
+            'zimbrasingle',
+            __FUNCTION__,
+            $params,
+            "Connection test to " . $params['serverip'] . " failed: Cannot login",
+            $login->getMessage()
+        );
+        return array(
+            'success' => false,
+            'error' => "Connection test to " . $params['serverip'] . " failed, the error was: " . $login->getMessage(),
+        );
+    } else {
+        return array(
+            'success' => true,
+            'error' => '',
+        );
+    }
+}
+
+function zimbraSingle_UsageUpdate($params)
+{
+}
+
+function zimbraSingle_ClientArea($params)
+{
+    $response = zimbraSingleClientArea($params['customfields']);
+    return array(
+        'templatefile' => 'clientarea',
+        'vars' => array(
+            'webmailURL' => $response,
+        ),
+    );
+}
+
+function zimbraSingle_ChangePassword($params)
+{
+    $response = zimbraSingleChangePassword($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return $response;
+}
+
+function zimbraSingle_CreateAccount($params)
+{
+    $response = zimbraSingleCreateAccount($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return 'Error creating account';
+}
+
+function zimbraSingle_SuspendAccount($params)
+{
+    $response = zimbraSingleSuspendAccount($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return 'Error suspending account';
+}
+
+function zimbraSingle_UnsuspendAccount($params)
+{
+    $response = zimbraSingleUnsuspendAccount($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return 'Error unsuspending account';
+}
+
+function zimbraSingle_TerminateAccount($params)
+{
+    $response = zimbraSingleDeleteAccount($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return 'Error deleting account';
+}
+
+function zimbraSingle_ChangePackage($params)
+{
+    $response = zimbraSingleChangePackage($params['customfields']);
+    if($response) {
+        return 'success';
+    }
+    return 'Error deleting account';
+}
+
+function zimbraSingle_genUsername($name)
+{
+    /*    $namelen = strlen($name);
+    $result = select_query("tblhosting","COUNT(*)",array("username" => $name));
+    $data = mysql_fetch_array($result);
+    $username_exists = $data[0];
+    $suffix=0;
+
+    while ($username_exists > 0) {
+        $suffix++;
+        $name = substr($name,0,$namelen).$suffix;
+
+        $result = select_query( "tblhosting", "COUNT(*)", array( "username" => $name ) );
+        $data = mysql_fetch_array($result);
+
+        $username_exists = $data[0];
+    }
+
+    return $name; */
+}