Die LimeSurvey RemoteControl API – eine kritische Betrachtung

Eine Schnittstelle, die funktioniert, aber ...

LimeSurvey bietet seit Version 2.0 eine Fernsteuerungsschnittstelle: die RemoteControl 2 API, kurz LSRC2. Über JSON-RPC oder XML-RPC lassen sich Umfragen anlegen, aktivieren, Teilnehmer einladen, Antworten exportieren – praktisch alles, was auch über das Webinterface möglich ist. Die Schnittstelle ist funktional, breit eingesetzt und für viele Integrationen die einzige Option.

Und sie trägt dieselben Merkmale wie der Rest des Systems.


Was die API kann

Der Funktionsumfang ist beeindruckend. Umfragen anlegen, importieren, kopieren, aktivieren und löschen. Gruppen und Fragen verwalten. Teilnehmer hinzufügen, einladen, erinnern. Antworten lesen, schreiben, exportieren. Benutzer und Berechtigungen abfragen. Quoten verwalten. Feldkarten abrufen.

Für Integrationen mit externen Systemen – CRM, ERP, eigene Applikationen – ist das ein mächtiges Werkzeug. Es gibt kaum einen Anwendungsfall rund um LimeSurvey, der sich nicht über diese Schnittstelle abbilden lässt.

Das macht eine ehrliche Betrachtung ihrer Schwächen umso wichtiger. Wer die API einsetzt, muss wissen, womit er arbeitet.


Session-Management statt stateless Authentication

Das erste Signal kommt aus dem Protokolldesign. Jede Sitzung beginnt mit einem expliziten Login:

$sessionKey = $client->get_session_key($username, $password);

Der zurückgegebene Session-Key wird dann als erster Parameter an jeden weiteren Aufruf übergeben. Am Ende:

$client->release_session_key($sessionKey);

Das ist ein zustandsbehaftetes Modell – und es stammt aus einer Zeit, in der REST und stateless Authentication noch keine etablierten Standards waren. Moderne APIs authentifizieren pro Request, über Bearer Token oder API Keys. Es gibt keinen globalen State, keine vergessbare Session, kein explizites Logout das fehlen kann.

Das LSRC2-Modell hat konkrete Konsequenzen: Wer release_session_key nicht aufruft, hinterlässt verwaiste Sessions in der Datenbank. _checkSessionKey() bereinigt zwar abgelaufene Sessions bei jedem Aufruf – aber das ist Housekeeping als Nebeneffekt, kein Design.


Das Fehlerbehandlungs-Problem

Das tiefste architektonische Problem der API ist nicht das Protokoll. Es ist die Fehlerbehandlung – oder genauer: die fehlende Konvention dafür.

Jede Methode kann mehrere verschiedene Rückgabetypen haben. add_survey gibt bei Erfolg einen Integer zurück:

return (int) $iNewSurveyid;

delete_survey gibt bei Erfolg ein Array zurück:

return array('status' => 'OK');

copy_survey gibt bei Erfolg ein Array mit zwei Schlüsseln zurück:

return array('status' => 'OK', 'newsid' => $aImportResults['newsid']);

Und Fehler sehen so aus:

return array('status' => 'No permission');
return array('status' => self::INVALID_SESSION_KEY);
return array('status' => 'Error: Invalid survey ID');
return array('status' => 'Faulty parameters');

Das bedeutet: Der Aufrufer kann nicht am Rückgabetyp erkennen, ob ein Aufruf erfolgreich war. Er muss den Typ prüfen, dann den Inhalt prüfen, und dabei wissen, welches Format diese konkrete Methode im Erfolgsfall zurückgibt. Für jede Methode einzeln.

Das ist kein Fehler in einer Methode – es ist das fehlende Kontrakt-Design der gesamten Schnittstelle. In einer REST-API kommuniziert der HTTP-Statuscode den Erfolg oder Fehler, unabhängig vom Response-Body. In einer RPC-API würde ein typisiertes Response-Objekt das leisten. Hier leistet es nichts.


remotecontrol_handle – wieder ein Helper

Die Implementierung der API liegt in application/helpers/remotecontrol/remotecontrol_handle.php. Kein Namespace. Kein Interface. Keine abstrakte Basisklasse. Eine Klasse, die alle API-Methoden in sich vereint – über 3000 Zeilen.

Das Muster kennen wir: Es ist derselbe Ansatz wie common_helper.php und createFieldMap(), nur als Klasse verkleidet. Die gesamte Fernsteuerungslogik – Session-Verwaltung, Authentifizierung, Survey-Operationen, Token-Management, Response-Export – in einer einzigen Klasse ohne erkennbare Schichtenarchitektur.

Yii::app()->loadHelper() taucht mitten in API-Methoden auf:

public function add_survey(...)
{
    Yii::app()->loadHelper('surveytranslator');
    // ...
}

public function export_statistics(...)
{
    Yii::app()->loadHelper('admin.statistics');
    // ...
}

Die Helper-Abhängigkeiten, die wir im Artikel über die Helper-Funktionen betrachtet haben, setzen sich nahtlos in die API fort. Der Remote-Control-Handle ist kein sauberer Adapter zwischen Protokoll und Domäne – er ist ein weiterer Aggregationspunkt für globale Abhängigkeiten.


_checkSessionKey() – inkonsistent eingesetzt

Jede Methode beginnt mit einer Session-Prüfung. Aber das Muster variiert:

// Variante 1: if mit verschachteltem Erfolgsblock
if ($this->_checkSessionKey($sSessionKey)) {
    // ... gesamte Methode
} else {
    return array('status' => self::INVALID_SESSION_KEY);
}

// Variante 2: early return
if (!$this->_checkSessionKey($sSessionKey)) {
    return array('status' => self::INVALID_SESSION_KEY);
}

Beide Muster existieren nebeneinander in derselben Klasse. Das ist keine inhaltliche Inkonsistenz – beide Varianten prüfen korrekt. Aber es ist ein Signal: Die Klasse wurde über lange Zeit von verschiedenen Entwicklern erweitert, ohne dass ein einheitliches Muster vereinbart oder durchgesetzt wurde.


Copy-Paste als Architekturmuster

invite_participants() und mail_registered_participants() enthalten denselben 20-Zeilen-Regex zur E-Mail-Validierung – zweimal, identisch, ohne gemeinsame Hilfsmethode. Es gibt zwar _checkEmailFormat() als geschützte Methode in derselben Klasse. Aber sie wird von diesen Methoden nicht verwendet.

Das ist das demoMode-Guard-Muster aus LimeMailer in neuem Kontext: eine querschneidende Logik, die nicht als solche behandelt wird, sondern in jeden Aufrufer hineinkopiert wird.


Hungarian Notation, konsequent

$sSessionKey, $iSurveyID, $aSurveySettings, $bCreateToken, $sDocumentType – die Präfix-Konvention aus dem Hungarian-Notation-Artikel ist in der gesamten API-Schicht konsequent durchgehalten. Nicht als bewusste Entscheidung, sondern als historisches Artefakt: die Klasse ist so alt wie die Konvention, und beide sind gemeinsam gewachsen.


Der empfohlene Client

Die offizielle Dokumentation empfiehlt den JsonRPCClient aus dem weberhofer/jsonrpcphp-Repository als PHP-Client. Die Klasse stammt aus dem Jahr 2007, wurde 2012 leicht überarbeitet, und sieht so aus:

public function __call($method, $params)
{
    $request = array(
        'method' => $method,
        'params' => $params,
        'id' => $currentId
    );
    $request = json_encode($request);
    // HTTP POST via cURL oder fopen
    $response = json_decode($response, true);
    return $response['result'];
}

__call() als Magic Method ist für einen generischen RPC-Client elegant: jeder Methodenaufruf wird automatisch als JSON-RPC-Request serialisiert. Aber das Ergebnis ist mixed – ein rohes Array, das die Antwort der API trägt. Was darin steckt, weiß der Client nicht. Ob der Aufruf erfolgreich war, weiß der Client nicht. Ob result ein Integer, ein Array mit status => OK oder ein Array mit status => No permission ist – das muss der Aufrufer selbst herausfinden.

Der Client ist ein generischer Transport. Er kennt keine LimeSurvey-Konzepte. Er ist keine API – er ist ein HTTP-Wrapper.


Die REST-API als offene Baustelle

Im Repository existiert seit Jahren ein Verzeichnis application/libraries/Api. Eine REST-API ist dokumentiert, verlinkt – und liefert 404. Das LimeSurvey-Forum ist eindeutig: die REST-API ist in Entwicklung, aber nicht verfügbar. Es gibt einen Drittanbieter-Plugin-Versuch (machitgarha/limesurvey-rest-api), der zeigt, wie sehr die Community eine moderne Schnittstelle vermisst.

Die JSON-RPC-API ist damit nicht eine Übergangslösung – sie ist die einzige Lösung. Für alle, die heute mit LimeSurvey integrieren wollen, gibt es keine Alternative.


Fazit

Die LSRC2-API funktioniert. Sie ist produktiv im Einsatz, sie hat eine große Nutzerbasis, und für viele Integrationsszenarien ist sie ausreichend. Das ist nicht wenig.

Aber sie trägt dieselben Muster wie der Rest des Systems: fehlende Konventionen für Fehlerbehandlung, implizite Abhängigkeiten, Copy-Paste statt Abstraktion, ein Rückgabe-Typsystem das keines ist. Wer mit der API arbeitet, trägt die Last dieser Entscheidungen – nicht als gelegentliches Problem, sondern als konstante Hintergrundarbeit beim Interpretieren von Rückgabewerten.

Der nächste Artikel zeigt, wie ein typisierter Client darüber aussehen könnte – eine Schicht, die das Protokoll kennt und die Domäne spricht.