Server-Module erstellen

Aus sourceDESK Wiki
Version vom 8. November 2019, 23:30 Uhr von Richard Reiber (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Um Hosting-Produkte automatisch einzurichten, können Sie auch eigene Server-Module schreiben.

Dateistruktur

Ein Server-Modul bekommt ein eigenes Verzeichnis unter modules/provisioning. In diesem Verzeichnis muss sich eine PHP-Datei mit dem gleichen Namen wie das Verzeichnis und der Endung .php befinden. In dieser PHP-Datei wird eine Klasse definiert, die von der Klasse Provisioning erbt.

Attribute

Es werden mehrere Attribute innerhalb der erstellten Klasse benötigt, die höchstens protected sein dürfen:

  • $name gibt einen Anzeigenamen für das Modul an
  • $short gibt den Kurznamen des Moduls an, das entspricht dem Verzeichnis- bzw. Dateinamen

Konfiguration

Es wird eine Methode Config($id, $product = true) benötigt, diese bekommt eine Produkt-ID ($product = true) oder Vertrags-ID ($product = false) als Argument. Hier können Sie Form-Elemente erstellen. Bitte geben Sie Eingabefelder die CSS-Klasse prov-settings und ein Attribut data-setting mit dem Kurznamen der Einstellung, damit die Daten per Ajax übernommen werden können.

Beispiel:

<input type="text" data-setting="domain" value="<?=$this->getOption("domain");?>" placeholder="sourceway.de" class="form-control prov_settings" />

Sie können auch die Server-Verbindung prüfen lassen oder Daten vom Server abrufen - Beispiele hierzu finden Sie in den verschiedenen mitgelieferten Server-Modulen.

Vertrag erstellen

Sie benötigen eine Methode Create($id). Diese erhält die ID des Vertrages als Argument und soll den Vertrag einrichten. Zunächst sollten Sie $this->loadOptions($id) ausführen, um die Vertragsdetails zu laden. Wenn Sie die Kundendaten benötigen, können Sie mit $u = $this->getClient($id) eine Instanz der Klasse User abrufen.

Ein Zugriff auf Modul-Einstellungen ist mit $this->getOption("SHORT_NAME") möglich. Als Rückgabe der Funktion wird ein zweidimensionales Array erwartet. Im Erfolgsfall enthält das Array an erster Stelle true und an zweiter Stelle ein Array mit Vertragsdetails. Im Fehlerfall muss an erster Stelle false und an zweiter Stelle eine Fehlermeldung als String stehen.

Vertrag löschen

Wenn Sie die Löschung eines bestehenden Vertrages ebenfalls in sourceDESK integrieren möchten, können Sie die Methode Delete($id) implementieren. Diese muss ein Array zurückgeben, das im Erfolgsfall ein Element - true - besitzt und im Fehlerfall ein Element false sowie einen Fehlerstring als zweites Element zurückgibt.

Vertrag sperren / entsperren

Über die Methoden Suspend($id) und Unsuspend($id) können Sie Verträge direkt aus sourceDESK sperren oder wieder entsperren. Die Rückgabe wird wie bei der Löschung eines Vertrages erwartet.

Für Schnittstellen, die nur die Übergabe eines Status verlangen, also keine separaten Endpunkte für Sperrung und Entsperrung haben, können Sie wie folgt arbeiten:

public function Suspend($id, $status = 1) {
    $this->loadOptions($id);
    // ...
    return Array(true);
}
 
public function Unsuspend($id) {
    return $this->Suspend($id, 0);
}

Wichtig ist hierbei, dass der zweite Parameter von Suspend() einen Standard-Wert bekommt, da sourceDESK die Funktion ansonsten nicht aufrufen kann.

Paket wechseln

Über die Methode ChangePackage($id) können Sie einen Paketwechsel direkt aus sourceDESK initiieren. Nach Wechsel des Produktes können Sie so die Änderungen direkt an den Server übermitteln. Das Rückgabeformat entspricht dem der Lösch- und Sperr-Methoden.

Modul-Ausgabe

Sie können und sollten über die Methode Output($id, $task = "") HTML-Code zurückgeben, der im Kundenbereich und in der Administration angezeigt wird. Diese Methode behandelt auch den Aufruf Ihrer eigenen Aktionen, dann ist das Argument $task mit der entsprechenden Aktion gefüllt.

Änderungen an Kunden-Daten

Seit Version 1.1.3 unterstützt sourceDESK die Weitergabe von Änderungen an Kundendaten an Provisioning-Module. Sobald die Stammdaten des Kunden geändert werden, wird die Methode ClientChanged($id, $new) des Moduls aufgerufen, sofern diese vorhanden ist. Hierbei ist $id die Vertrags-ID und $new ein Array mit den geänderten Kundenfeldern. Eine Implementierung könnte so aussehen:

public function ClientChanged($id, array $changedFields) {
    if (!count(array_intersect($changedFields, ["name", "mail"]))) {
        return;
    }
 
    $this->loadOptions($id);
    $c = $this->getClient($id);
 
    $this->Call("2/UpdateUser", [
        "LoginName" => $this->getData("username"),
        "DisplayName" => $c->get()['name'],
        "Email" => $c->get()['mail'],
    ]);
}

Benutzer-Speicher

Sie können für jeden Kunden - unabhängig vom Vertrag - Daten speichern. Das eignet sich zum Beispiel, wenn Sie mehrere Verträge mit einem Modul erstellen, via API aber immer den gleichen Account nutzen möchten. Das funktioniert wie folgt:

// Wir befinden uns innerhalb einer Modul-Methode
$user = $this->getClient($id);
$user->setAccount("plesk", ["username" => "abcdef", "id" => 123]); // Zweites Argument muss unbedingt ein Array sein
 
// Zu einem späteren Zeitpunkt
$data = $user->getAccount("plesk");

Zu jedem Schlüssel (im Beispiel "plesk") wird nur ein Account gespeichert. Dieser kann jederzeit überschrieben werden.

E-Mailvariablen

sourceDESK unterstützt den Versand von Willkommens-Emails an Kunden. Hier benötigen Sie eine Funktion AllEmailVariables(), die ein Array mit Namen von verfügbaren E-Mailvariablen zurückgibt. Weiter wird eine Methode EmailVariables($id) benötigt, die die Variablen mit konkreten Werten füllt.

Ein Beispiel:

public function AllEmailVariables() {
    return Array(
        "url",
        "user",
        "password",
    );
}
 
public function EmailVariables($id) {
    $this->loadOptions($id);
 
    return Array(
        "url" => $this->getOption("url"),
        "user" => $this->getData("username"),
        "password" => $this->getData("password"),
    );
}

Eigene Funktionen

Sie können eigene Funktionen mit der Methode OwnFunctions($id) implementieren. Diese Funktionen stehen im Kundenbereich zur Verfügung. Funktionen, die Administratoren zur Verfügung stehen sollen, müssen durch die Methode AdminFunctions($id) zurückgegeben werden. Erwartet wird jeweils ein Array, das als Schlüssel die Aktionsnamen und als Wert die Aktionsbeschreibung enthält.

Ein Beispiel zur Definition der Funktionen:

public function OwnFunctions($id) {
    return Array(
        "reset_mail" => "E-Mailadresse zurücksetzen",
        "reset_pw" => "Passwort zurücksetzen",
        "reset_rights" => "Rechte zurücksetzen",
    );
}
 
public function AdminFunctions($id) {
    return Array(
        "reset_mail" => "E-Mailadresse zurücksetzen",
        "reset_pw" => "Passwort zurücksetzen",
        "reset_rights" => "Rechte zurücksetzen",
    );
}

Die Implementierung der Aktionen erfolgt in der Methode Output($id, $task = ""). $task ist dann beispielsweise reset_mail. In Ihrer Ausgabe können Sie dann sowohl weitere Daten abfragen als auch eine Rückmeldung an den Kunden geben.

API-Integration

Sie können eigene Aufgaben definieren, die über die Hosting-API vom Kunden aufgerufen werden können. Dafür müssen Sie in der Methode ApiTasks die verfügbaren Aufgaben und aufzurufenden Methoden definieren, beispielsweise:

public function ApiTasks($id) {
    return Array(
        "SetUserPassword" => "pwd",
        "GetUserSites" => "",
        "AddUserSite" => "name,url",
        "DeleteUserSite" => "id",
    );
}

Die entsprechenden Methoden werden mit den Parametern $id (ID des Hosting-Vertrags) und $req (Array mit Parametern) aufgerufen.

Beispiel-Code

<?php
 
class CPanelProv extends Provisioning {
	protected $name = "cPanel";
	protected $short = "cpanel";
	protected $lang;
	protected $options = Array();
 
	public function Config($id, $product = true) {
		$this->loadOptions($id, $product);
 
		if (isset($_POST['url'])) {
            $res = $this->Call("listpkgs", [], $_POST);
 
            if (!$res) {
				die('<div class="alert alert-danger"><b>Fehler!</b> Keine Verbindung zum Server m&ouml;glich. Eventuell SSL-Zertifikat pr&uuml;fen.</div>');
            }
 
			if (!empty($res->cpanelresult->error)) {
				die('<div class="alert alert-danger"><b>Fehler!</b> ' . htmlentities($res->cpanelresult->error) . '</div>');
            }
 
            $pkgs = $res->data->pkg;
 
			if (count($pkgs) == 0) {
				die('<div class="alert alert-warning">Es existieren keine Angebote in cPanel.</div>');
			}
 
			$html = '<div class="form-group"><label>Angebot</label><select data-setting="plan" class="form-control prov_settings">';
 
			foreach ($pkgs as $p) {
				$p = $p->name;
				if (!empty($this->getOption("plan")) && $this->getOption("plan") == $p) {
					$html .= "<option selected='selected'>$p</option>";
				} else {
					$html .= "<option>$p</option>";
				}
 
			}
 
			$html .= "</select></div>";
			die($html);
		}
 
		ob_start();?>
 
		<div class="row">
		<input style="opacity: 0;position: absolute;">
        <input type="password" style="opacity: 0;position: absolute;">
 
			<div class="col-md-4">
				<div class="form-group">
					<label>WHM-URL</label>
					<input type="text" data-setting="url" value="<?=$this->getOption("url");?>" placeholder="https://webspacepanel.de:2087" class="form-control prov_settings" />
				</div>
			</div>
 
			<div class="col-md-4">
				<div class="form-group">
					<label>WHM-Benutzer</label>
					<input type="text" data-setting="user" value="<?=$this->getOption("user");?>" placeholder="root" class="form-control prov_settings" />
				</div>
			</div>
 
			<div class="col-md-4">
				<div class="form-group">
					<label>WHM-Passwort</label>
					<input type="password" data-setting="password" value="<?=$this->getOption("password");?>" placeholder="L957fZviq6" class="form-control prov_settings" />
				</div>
			</div>
		</div>
 
		<a href="#" id="check_conn" class="btn btn-default btn-block">Daten vom Server abrufen</a>
 
		<script>
		var doing = false;
		function request(){
			if(doing) return;
			doing = !doing;
			$("#check_conn").html('<i class="fa fa-spin fa-spinner"></i> Daten werden abgerufen...');
			$.post("?p=<?php if ($product) {?>product_<?php }?>hosting&id=<?=$_GET['id'];?>&module=cpanel", {
				url: $("[data-setting=url]").val(),
				user: $("[data-setting=user]").val(),
				password: $("[data-setting=password]").val(),
				"csrf_token": "<?=CSRF::raw(); ?>",
			}, function(r){
				doing = false;
				$("#check_conn").html('Daten vom Server abrufen');
				$("#server_conf").html(r);
			});
		}
		<?php if (!empty($this->getOption("url"))) {
			echo 'request();';
		}
		?>
 
		$("#check_conn").click(function(e){
			e.preventDefault();
			request();
		});
		</script>
 
		<br /><div id="server_conf"></div>
 
		<?php
        $res = ob_get_contents();
		ob_end_clean();
		return $res;
	}
 
	private function Call($func, $data = [], $opt = false) {
		if (!is_array($opt)) {
			$opt = $this->options;
        }
 
        $data = is_array($data) && count($data) > 0 ? "&" . http_build_query($data) : "";
 
        $ch = curl_init(rtrim($opt["url"], "/") . "/json-api/" . $func . "?api.version=1" . $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            "Authorization: Basic " . base64_encode($opt["user"] . ":" . $opt["password"]) . "\n\r",
        ]);
        $res = json_decode(curl_exec($ch));
        curl_close($ch);
 
        return is_object($res) ? $res : false;
    }
 
    public function Create($id) {
		global $sec;
		$this->loadOptions($id);
 
        $res = $this->Call("createacct", [
            "username" => "web$id",
            "domain" => "$id.web",
            "password" => $pwd = $sec->generatePassword(16, false, "lud"),
            "plan" => $this->getOption("plan"),
        ]);
 
        if ($res->metadata->result) {
            return [true, [
                "password" => $pwd,
            ]];
        } else {
            return [false, $res->metadata->reason];
        }
	}
 
	public function Delete($id) {
		$this->loadOptions($id);
 
        $res = $this->Call("removeacct", [
            "username" => "web$id",
        ]);
 
        if ($res->metadata->result) {
            return [true];
        } else {
            return [false, $res->metadata->reason];
        }
	}
 
	public function Output($id, $task = "") {
		global $pars, $raw_cfg, $CFG;
		$this->loadOptions($id);
 
        ob_start();
 
        $org = $this->getOption("url");
        $host = array_shift(explode(":", rtrim(substr(array_pop(explode(":", $org, 2)), 2), "/")));
        $url = "https://$host:2083/";
 
		?>
		<div class="row">
			<div class="col-md-6">
				<div class="panel panel-default">
				  <div class="panel-heading">cPanel-Zugangsdaten</div>
				  <div class="panel-body">
				    <b>URL:</b> <a target="_blank" href="<?=$url;?>"><?=$url;?></a><br />
				    <b>Benutzername:</b> web<?=$id;?><br />
				    <b>Passwort:</b> <?=$this->getData("password"); ?></a>
				  </div>
				</div>
			</div>
 
			<div class="col-md-6">
				<div class="panel panel-default">
				  <div class="panel-heading">FTP-Zugangsdaten</div>
				  <div class="panel-body">
				    <b>Host:</b> <?=$host; ?><br />
				    <b>Benutzername:</b> web<?=$id;?><br />
				    <b>Passwort:</b> <?=$this->getData("password");?>
				  </div>
				</div>
			</div>
		</div>
		<?php
        $res = ob_get_contents();
		ob_end_clean();
		return $res;
	}
 
	public function Suspend($id, $do = "") {
		$this->loadOptions($id);
 
        $res = $this->Call($do . "suspendacct", [
            "user" => "web$id",
        ]);
 
        if ($res->metadata->result) {
            return [true];
        } else {
            return [false, $res->metadata->reason];
        }
	}
 
	public function Unsuspend($id) {
		return $this->Suspend($id, "un");
	}
 
	public function ChangePackage($id) {
        $this->loadOptions($id);
 
        $res = $this->Call("changepackage", [
            "user" => "web$id",
            "pkg" => $this->getOption("plan"),
        ]);
 
        if ($res->metadata->result) {
            return [true];
        } else {
            return [false, $res->metadata->reason];
        }
	}
 
	public function AllEmailVariables() {
		return Array(
			"url",
		);
	}
 
	public function EmailVariables($id) {
		global $raw_cfg;
		$this->loadOptions($id);
		$u = $this->getClient($id);
 
		return Array(
			"url" => $raw_cfg['PAGEURL'] . "/hosting/" . $id,
		);
	}
}