<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>LimeSurvey on markob.de</title><link>https://markob.de/limesurvey/</link><description>Recent content in LimeSurvey on markob.de</description><generator>Hugo</generator><language>de-DE</language><lastBuildDate>Mon, 24 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://markob.de/limesurvey/index.xml" rel="self" type="application/rss+xml"/><item><title>Das Framework ist nur ein Detail. Das Domainmodell ist es nicht.</title><link>https://markob.de/limesurvey/domain-model/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/domain-model/</guid><description>&lt;p&gt;In &lt;a href="https://markob.de/limesurvey/yii3"&gt;Das Framework ist nur ein Detail&lt;/a&gt; war unsere These: Neue Anwendungslogik sollte nicht davon abhängen, ob am äußeren Rand Yii, Slim oder ein anderes Framework HTTP-Requests verarbeitet. Das Framework sollte Adapter sein, nicht Architektur.&lt;/p&gt;
&lt;p&gt;Daran hat sich nichts geändert. Inzwischen beschäftigt uns allerdings eine Frage, die noch eine Ebene tiefer geht: Wenn wir das Framework aus dem Zentrum von LimeSurvey entfernen – welches Domainmodell wollen wir dort eigentlich erhalten? Begriffe wie &lt;code&gt;QuestionGroup&lt;/code&gt;, &lt;code&gt;Question&lt;/code&gt;, &lt;code&gt;SubQuestion&lt;/code&gt; oder &lt;code&gt;QuestionType&lt;/code&gt; sind für LimeSurvey-Entwickler so selbstverständlich, dass man leicht vergisst, sie überhaupt noch infrage zu stellen. Aber sind das notwendige Konzepte eines Survey-Systems – oder historische Architekturentscheidungen, die uns heute nur deshalb wie Domainlogik erscheinen, weil LimeSurvey seit vielen Jahren so funktioniert?&lt;/p&gt;</description></item><item><title>Das Framework ist nur ein Detail</title><link>https://markob.de/limesurvey/yii3/</link><pubDate>Fri, 21 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/yii3/</guid><description>&lt;p&gt;In einer &lt;a href="https://careers.limesurvey.org/jobs/Careers/38813000007296145/Full-Stack-Developer-m-f-d"&gt;LimeSurvey-Stellenanzeige für einen Full-Stack-Developer&lt;/a&gt; (Stand: 20. August 2026) taucht unter „Tech We Use&amp;quot; das „Yii-3 PHP Framework&amp;quot; auf – bemerkenswert, denn der aktuelle LimeSurvey-Core basiert weiterhin auf Yii 1.1. Ob damit der bestehende Core migriert werden soll, ob Yii3 nur für neue Komponenten gedacht ist oder ob lediglich einzelne Yii3-Pakete gemeint sind, wissen wir nicht. Für das Folgende spielt das auch keine große Rolle.&lt;/p&gt;
&lt;p&gt;Denn bevor LimeSurvey entscheidet, welches Framework Yii 1.1 irgendwann ersetzt, lohnt sich eine andere Frage: Warum sollte diese Entscheidung für den Anwendungscode überhaupt von Bedeutung sein?&lt;/p&gt;</description></item><item><title>Zweimal `square()` - Teil 3</title><link>https://markob.de/limesurvey/zweimal-square-teil-3/</link><pubDate>Wed, 19 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/zweimal-square-teil-3/</guid><description>&lt;p&gt;Im zweiten Teil dieser Geschichte ging es um eine unbequeme Erkenntnis: Zwei zehn Jahre lang getrennt gewachsene Statistics-Implementierungen lassen sich nicht einfach wieder zusammenführen. Ein Diff zeigt, dass sich Admin und Public Statistics unterscheiden – er sagt nicht, ob dieser Unterschied fachlich notwendig ist oder nur historische Drift.&lt;/p&gt;
&lt;p&gt;Also erst verstehen, dann verändern. Characterization Tests, um vorhandenes Verhalten zu dokumentieren. Die versteckte &lt;code&gt;$_POST&lt;/code&gt;-Abhängigkeit sichtbar machen, als ersten kleinen Schritt zu einer Trust Boundary.&lt;/p&gt;</description></item><item><title>Zweimal `square()` - Teil 2</title><link>https://markob.de/limesurvey/zweimal-square-teil-2/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/zweimal-square-teil-2/</guid><description>&lt;p&gt;Im ersten Teil dieser Geschichte führte uns eine SQL Injection zu zwei Statistics-Helpern, zwei &lt;code&gt;buildSelects()&lt;/code&gt;-Funktionen und schließlich zu zwei Funktionen, die nichts weiter tun, als eine Zahl zu quadrieren.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$number&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$squarenumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$squarenumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$number&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$squarenumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zweimal.&lt;/p&gt;
&lt;p&gt;Die Git-Historie führte zurück ins Jahr 2016. Für die Public Statistics wurde damals ein großer Teil der bestehenden Statistics-Implementierung abgespalten. Seitdem entwickelten sich Admin Statistics und Public Statistics getrennt weiter – bis eine SQL Injection zehn Jahre später beide Zweige wieder einholte.&lt;/p&gt;</description></item><item><title>Zweimal `square()` - Teil 1</title><link>https://markob.de/limesurvey/zweimal-square-teil-1/</link><pubDate>Mon, 17 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/zweimal-square-teil-1/</guid><description>&lt;p&gt;Manchmal beginnt Code-Archäologie nicht mit einem spektakulären Fund. Manchmal beginnt sie mit einem SQL-Injection-Fix – und einer Funktion, die eine Zahl quadriert.&lt;/p&gt;
&lt;p&gt;Am 12. August 2026 wurde im LimeSurvey-Repository Pull Request &lt;a href="https://github.com/LimeSurvey/LimeSurvey/pull/5225"&gt;#5225&lt;/a&gt; eröffnet. Der Titel: &amp;ldquo;Fixed issue #20648: [security] SQL injection in statistics&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Das Problem war ernst. In der Statistikfunktion konnten manipulierte Filterparameter dazu führen, dass vom Request stammende Feldnamen ungeprüft in SQL-Abfragen gelangten. Der Fix führte deshalb eine Allowlist ein: Nur Spalten, die tatsächlich im Schema der jeweiligen Response-Tabelle existieren, dürfen noch als Feldnamen verwendet werden&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description></item><item><title>Naming ist wichtig</title><link>https://markob.de/limesurvey/naming-ist-wichtig/</link><pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/naming-ist-wichtig/</guid><description>&lt;p&gt;Manchmal reicht ein einzelner Feldname, um zu zeigen, wie viel Architektur eigentlich in einer Namensentscheidung steckt. Ein aktueller Pull Request im LimeSurvey-Kernrepository liefert dafür ein besonders klares Beispiel.&lt;/p&gt;
&lt;h2 id="der-fall"&gt;Der Fall&lt;/h2&gt;
&lt;p&gt;Am 14. August 2026 wurde im LimeSurvey-Repository der Pull Request &lt;a href="https://github.com/LimeSurvey/LimeSurvey/pull/5236"&gt;#5236&lt;/a&gt; eingereicht: ein neues Feature, das Surveys um einen zusätzlichen, sprachunabhängigen Titel erweitert – gedacht als interne Bezeichnung zur Identifikation im Administrationsbereich, unabhängig vom öffentlich sichtbaren, lokalisierten Umfragetitel.&lt;/p&gt;
&lt;p&gt;Technisch ist das ein sinnvolles Feature. Der bestehende Titel einer Survey (&lt;code&gt;survey_languagesettings.surveyls_title&lt;/code&gt;) ist an eine Sprache gebunden und für interne Zwecke – etwa das Wiederfinden einer Umfrage in einer langen Liste – oft ungeeignet. Ein zweites, sprachneutrales Feld schließt eine echte Lücke.&lt;/p&gt;</description></item><item><title>Report-driven Survey Design</title><link>https://markob.de/limesurvey/report-driven-survey-design/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/report-driven-survey-design/</guid><description>&lt;p&gt;Ich wollte eine Treiberanalyse bauen. Dafür brauchte ich eine Matrixfrage – mehrere Aspekte als Subquestions, einheitliche Skala. Dazu eine separate Gesamtbewertung als Zielgröße. Erst nachdem diese Struktur stand, habe ich überlegt, welche Aspekte ich überhaupt abfragen wollte.&lt;/p&gt;
&lt;p&gt;Irgendwann fiel mir auf:&lt;/p&gt;
&lt;p&gt;Ich entwerfe gar keine Umfragen mehr. Ich entwerfe Reports.&lt;/p&gt;
&lt;h2 id="das-problem-mit-der-klassischen-reihenfolge"&gt;Das Problem mit der klassischen Reihenfolge&lt;/h2&gt;
&lt;p&gt;Der übliche Weg beim Fragebogendesign: Was wollen wir wissen? Welche Fragen stellen wir? Welcher Fragetyp passt? Der Report kommt am Ende – als Auswertung dessen, was die Daten hergeben.&lt;/p&gt;</description></item><item><title>Warum Vergütung auf Platz 7 landet</title><link>https://markob.de/limesurvey/priority-score/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/priority-score/</guid><description>&lt;p&gt;In einer Employee Retention Survey hat Vergütung &amp;amp; Benefits den größten Gap zum Zielwert. Score 2,6, Zielwert 4,2 – eine Lücke von 1,6 Punkten. Kein anderer Bereich liegt weiter vom Ziel entfernt.&lt;/p&gt;
&lt;p&gt;Trotzdem landet Vergütung auf Platz 7 von 8.&lt;/p&gt;
&lt;p&gt;Das ist kein Fehler. Es ist das Ergebnis einer bewussten Designentscheidung: nicht die Größe der Lücke allein bestimmt die Priorität, sondern die Lücke gewichtet nach strategischer Relevanz.&lt;/p&gt;
&lt;h2 id="das-problem-mit-dem-rohen-gap"&gt;Das Problem mit dem rohen Gap&lt;/h2&gt;
&lt;p&gt;Wer acht Dimensionen einer Mitarbeiterbefragung nach dem Gap zum Zielwert sortiert, bekommt eine Liste. Führung mit Score 2,8 liegt 1,4 Punkte unter dem Ziel. Vergütung mit Score 2,6 liegt 1,6 Punkte darunter. Vergütung hätte also Priorität.&lt;/p&gt;</description></item><item><title>Wo eine Verbesserung die meisten Menschen erreicht</title><link>https://markob.de/limesurvey/ipot-score/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/ipot-score/</guid><description>&lt;p&gt;&lt;a href="https://markob.de/priority-score"&gt;Im vorigen Artikel&lt;/a&gt; wurde gezeigt warum Vergütung trotz des größten Gaps auf Platz 7 landet: der Priority Score gewichtet nach strategischer Bedeutung, nicht nach Lückengröße. Führung mit 18 % Gewicht schlägt Vergütung mit 5 %.&lt;/p&gt;
&lt;p&gt;Aber der Priority Score hat einen blinden Fleck.&lt;/p&gt;
&lt;p&gt;Er fragt nicht: wie viele Mitarbeitende betrifft das Problem überhaupt?&lt;/p&gt;
&lt;h2 id="was-pri-nicht-sieht"&gt;Was PRI nicht sieht&lt;/h2&gt;
&lt;p&gt;Nehmen wir zwei Treiber aus dem Beispiel:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Treiber&lt;/th&gt;
 &lt;th&gt;Score&lt;/th&gt;
 &lt;th&gt;Gap&lt;/th&gt;
 &lt;th&gt;Gewicht&lt;/th&gt;
 &lt;th&gt;PRI&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Führung&lt;/td&gt;
 &lt;td&gt;2,8&lt;/td&gt;
 &lt;td&gt;1,4&lt;/td&gt;
 &lt;td&gt;18 %&lt;/td&gt;
 &lt;td&gt;0,252&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Vergütung&lt;/td&gt;
 &lt;td&gt;2,6&lt;/td&gt;
 &lt;td&gt;1,6&lt;/td&gt;
 &lt;td&gt;5 %&lt;/td&gt;
 &lt;td&gt;0,080&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PRI sagt: Führung zuerst. Das Modell argumentiert dass Führung strategisch wichtiger ist als Vergütung – und das stimmt.&lt;/p&gt;</description></item><item><title>Der NPS ist keine Zahl</title><link>https://markob.de/limesurvey/net-promoter-score/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/net-promoter-score/</guid><description>&lt;p&gt;NPS = 32. Gut oder schlecht?&lt;/p&gt;
&lt;p&gt;Die Frage klingt sinnvoll. Sie ist es nicht – zumindest nicht so gestellt.&lt;/p&gt;
&lt;p&gt;Ein Net Promoter Score ist mehr als eine Kennzahl im klassischen Sinne. Er ist eine verdichtete Verteilung. Hinter jedem NPS-Wert stecken drei Gruppen: Promotoren (Wertung 9–10), Passive (7–8) und Kritiker (0–6). Aus deren Anteilen ergibt sich die Formel:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;NPS = % Promotoren − % Kritiker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zwei völlig unterschiedliche Verteilungen können deshalb exakt denselben NPS erzeugen.&lt;/p&gt;</description></item><item><title>Likert-Skalen richtig einsetzen und auswerten</title><link>https://markob.de/limesurvey/likert-skalen/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/likert-skalen/</guid><description>&lt;p&gt;Wer eine Umfrage baut, kommt an Likert-Skalen kaum vorbei. Zustimmung zu einer Aussage von 1 bis 5, Zufriedenheit von „sehr unzufrieden&amp;quot; bis „sehr zufrieden&amp;quot;, Wichtigkeit von „unwichtig&amp;quot; bis „sehr wichtig&amp;quot; – das Grundprinzip ist immer dasselbe. Und doch entstehen bei der Umsetzung und Auswertung immer wieder dieselben Fehler.&lt;/p&gt;
&lt;p&gt;Dieser Artikel ist kein Statistik-Lehrbuch. Er beantwortet die Fragen, die sich beim Bauen und Interpretieren von Umfragen tatsächlich stellen.&lt;/p&gt;
&lt;h2 id="das-schulnotenproblem"&gt;Das Schulnotenproblem&lt;/h2&gt;
&lt;p&gt;Beginnen wir mit dem häufigsten Missverständnis.&lt;/p&gt;</description></item><item><title>Treiberanalyse: Welche Frage wirklich zählt</title><link>https://markob.de/limesurvey/treiberanalyse/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/treiberanalyse/</guid><description>&lt;p&gt;Eine Mitarbeiterbefragung mit zwanzig Zufriedenheitsfragen liefert zwanzig Balkendiagramme. Frage 7 schneidet schlecht ab, Frage 14 hervorragend, der Rest irgendwo dazwischen. Was macht man mit dieser Liste?&lt;/p&gt;
&lt;p&gt;Die naheliegende Reaktion ist: an den schlechtesten Werten ansetzen. Frage 7 verbessern, weil sie am schlechtesten bewertet wurde.&lt;/p&gt;
&lt;p&gt;Das ist oft die falsche Priorität.&lt;/p&gt;
&lt;h2 id="das-problem-mit-reiner-bewertung"&gt;Das Problem mit reiner Bewertung&lt;/h2&gt;
&lt;p&gt;Eine niedrig bewertete Frage ist nicht automatisch ein Problem das sich zu lösen lohnt. Es kommt darauf an, ob diese Frage überhaupt etwas bewegt.&lt;/p&gt;</description></item><item><title>Faker: Reproduzierbare Testdaten für LimeSurvey</title><link>https://markob.de/limesurvey/faker/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/faker/</guid><description>&lt;p&gt;Eine Umfrage ist fertig konzipiert, aber noch nicht live. Trotzdem soll bereits ein Report dafür entstehen – Trendcharts, KPI-Karten, Segmentierungen. Die Frage die sich sofort stellt: woher kommen die Daten, an denen man das alles testet?&lt;/p&gt;
&lt;p&gt;Die ehrliche Antwort war bei mir lange Zeit: ich habe sie von Hand erzeugt. Umfrage öffnen, durchklicken, Antworten setzen, abschicken. Wieder von vorne. Zwanzig, dreißig, manchmal fünfzig Mal – je nachdem wie überzeugend die Demo aussehen sollte.&lt;/p&gt;</description></item><item><title>Survey as Code: Umfragen versionieren, reviewen und wiederverwenden</title><link>https://markob.de/limesurvey/survey-as-code/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/survey-as-code/</guid><description>&lt;p&gt;&lt;em&gt;Früher war ich Software-Entwickler, heute bin ich Survey-Entwickler.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Als Software-Entwickler schreibe ich Code. Ich committe ihn, ich reviewe ihn, ich rolle ihn zurück wenn etwas schiefgeht. Jede Änderung hat eine Historie. Jede Funktion lässt sich wiederverwenden.&lt;/p&gt;
&lt;p&gt;Als Survey-Entwickler klicke ich Dropdown-Menüs.&lt;/p&gt;
&lt;p&gt;Das war zumindest bis vor kurzem so.&lt;/p&gt;
&lt;h2 id="das-problem-mit-dem-klicken"&gt;Das Problem mit dem Klicken&lt;/h2&gt;
&lt;p&gt;Eine LimeSurvey-Umfrage entsteht klassisch im Browser. Frage anlegen, Antwortoptionen eintippen, Fragengruppe verschieben, Bedingung setzen. Das funktioniert – bis die Umfrage zum zweiten Mal gebraucht wird.&lt;/p&gt;</description></item><item><title>Visualisierung allein reicht nicht mehr</title><link>https://markob.de/limesurvey/visualisierung-allein-reicht-nicht-mehr/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/visualisierung-allein-reicht-nicht-mehr/</guid><description>&lt;p&gt;Die meisten Umfragereports beantworten die Frage: „Was zeigen die Daten?&amp;quot; Die eigentlich wichtigere Frage lautet: „Was bedeuten die Daten?&amp;quot;&lt;/p&gt;
&lt;p&gt;Das ist kein semantischer Unterschied. Es ist der Unterschied zwischen einem Bericht, den jemand liest, und einem Bericht, den jemand versteht.&lt;/p&gt;
&lt;p&gt;Visualisierung bleibt wichtig. Sie ist jedoch nicht mehr ausreichend.&lt;/p&gt;
&lt;h2 id="das-visualisierungsproblem"&gt;Das Visualisierungsproblem&lt;/h2&gt;
&lt;p&gt;Vor fünfzehn Jahren war das beeindruckend:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Frage 1 ██████████ 42 %
Frage 2 ███████ 31 %
Frage 3 █████████ 58 %
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Heute kann das jede Software. LimeSurvey. Excel. SPSS. PowerBI. Tableau. ChatGPT. Ein Balkendiagramm pro Frage ist kein Mehrwert mehr – es ist der Mindeststandard.&lt;/p&gt;</description></item><item><title>Vom Fragebogen zum fertigen Report</title><link>https://markob.de/limesurvey/vom-fragebogen-zum-fertigen-report/</link><pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/vom-fragebogen-zum-fertigen-report/</guid><description>&lt;p&gt;Im &lt;a href="https://markob.de/professionelle-reports"&gt;ersten Artikel&lt;/a&gt; habe ich gezeigt was das Report-Plugin produziert. Dieser Artikel zeigt den kompletten Weg dorthin – von der Umfrage bis zum fertigen Report, Schritt für Schritt.&lt;/p&gt;
&lt;p&gt;Als Beispiel dient eine Restaurant-Feedback-Umfrage. Ein alltagsnahes Szenario: Gäste bewerten ihren Besuch, das Restaurant will verstehen was gut läuft und wo Verbesserungsbedarf besteht.&lt;/p&gt;
&lt;h2 id="die-umfrage"&gt;Die Umfrage&lt;/h2&gt;
&lt;p&gt;Die Umfrage besteht aus neun Fragen – kompakt, aber ausreichend für einen differenzierten Report.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Code&lt;/th&gt;
 &lt;th&gt;Typ&lt;/th&gt;
 &lt;th&gt;Frage&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;q1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;L – Radio List&lt;/td&gt;
 &lt;td&gt;Wie bewerten Sie Ihren Restaurantbesuch insgesamt? (1–6)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;nps&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;L – Radio List&lt;/td&gt;
 &lt;td&gt;Wie wahrscheinlich würden Sie uns weiterempfehlen? (0–10)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;matrix&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;F – Array&lt;/td&gt;
 &lt;td&gt;Wie bewerten Sie die folgenden Aspekte? (9 Subfragen, Skala 1–6)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;anlass&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;L – Radio List&lt;/td&gt;
 &lt;td&gt;Aus welchem Anlass waren Sie bei uns?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;besuchszeit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;L – Radio List&lt;/td&gt;
 &lt;td&gt;Wann haben Sie uns besucht?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;frequency&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;L – Radio List&lt;/td&gt;
 &lt;td&gt;Wie häufig besuchen Sie unser Restaurant?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;strengths&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;M – Multiple Choice&lt;/td&gt;
 &lt;td&gt;Welche Punkte sind Ihnen besonders positiv aufgefallen?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;potentials&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;O – List with Comment&lt;/td&gt;
 &lt;td&gt;Wo sehen Sie Verbesserungsbedarf?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;freetext&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T – Long Text&lt;/td&gt;
 &lt;td&gt;Möchten Sie uns noch etwas mitteilen?&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Wichtig dabei: die &lt;strong&gt;Fragen-Codes&lt;/strong&gt; – &lt;code&gt;q1&lt;/code&gt;, &lt;code&gt;nps&lt;/code&gt;, &lt;code&gt;matrix&lt;/code&gt; und so weiter. Diese Codes vergebe ich beim Anlegen der Fragen in LimeSurvey bewusst und sprechend. Sie sind der Ankerpunkt für das Mapping im nächsten Schritt.&lt;/p&gt;</description></item><item><title>Professionelle Reports für LimeSurvey</title><link>https://markob.de/limesurvey/professionelle-reports/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/professionelle-reports/</guid><description>&lt;p&gt;LimeSurvey sammelt Daten. Was danach kommt, ist meistens bekannt: CSV-Export, Excel, PowerPoint, Eigenentwicklung. Jedes Mal manuell, jedes Mal außerhalb von LimeSurvey, jedes Mal von vorne.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Das Plugin erzeugt keine generischen Statistiken. Es erzeugt Reports.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Das ist die Lücke zwischen zu wenig und zu viel. Auf der einen Seite die eingebaute Statistikansicht – immer verfügbar, kein Aufwand, aber generisch und nicht anpassbar. Auf der anderen Seite die vollständig externe Lösung – maximale Flexibilität, aber ohne direkten Bezug zur Umfrage. Dazwischen war bisher nichts.&lt;/p&gt;</description></item><item><title>LimeSurvey Fragetypen – Einordnung aus der Praxis</title><link>https://markob.de/limesurvey/fragetypen-einordnung/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/fragetypen-einordnung/</guid><description>&lt;p&gt;Die &lt;a href="https://markob.de/limesurvey/fragetypen"&gt;vollständige Referenz&lt;/a&gt; listet alle 28 Fragetypen auf. Dieser Artikel bewertet sie – aus der Perspektive von jemandem der seit Jahren LimeSurvey-Umfragen entwickelt, pflegt und erweitert.&lt;/p&gt;
&lt;p&gt;Die Frage ist nicht: Was kann dieser Typ? Die Frage ist: Wann ist er wirklich der richtige Ansatz?&lt;/p&gt;
&lt;h2 id="die-unverzichtbaren-typen"&gt;Die unverzichtbaren Typen&lt;/h2&gt;
&lt;p&gt;Das sind die Typen die in der Praxis fast immer ausreichen – und die man wirklich verstehen muss.&lt;/p&gt;
&lt;h3 id="liste-radio--l"&gt;Liste (Radio) · &lt;code&gt;L&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Der vielseitigste Einzelfragentyp. Vollständig konfigurierbare Antwortoptionen, optionale „Sonstige&amp;quot;-Option, gut für jede Einfachauswahl geeignet.&lt;/p&gt;</description></item><item><title>LimeSurvey Fragetypen – die vollständige Referenz</title><link>https://markob.de/limesurvey/fragetypen/</link><pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/fragetypen/</guid><description>&lt;p&gt;LimeSurvey kennt 28 Fragetypen. Jeder Typ wird intern durch ein einzelnes Zeichen identifiziert – ein Erbe aus der frühen Entwicklungsgeschichte des Systems, das bis heute in der Datenbank, im Code und in der RemoteControl-API sichtbar ist.&lt;/p&gt;
&lt;p&gt;Diese Referenz listet alle Typen mit ihrem internen Kürzel, ihrer Struktur und einem typischen Anwendungsfall. Eine Einordnung – welche Typen in der Praxis tragen und welche nicht – folgt in einem separaten Artikel.&lt;/p&gt;
&lt;h2 id="aufbau-der-tabelle"&gt;Aufbau der Tabelle&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kürzel&lt;/strong&gt; – das interne Zeichen wie es in der Datenbank (&lt;code&gt;questions.type&lt;/code&gt;) und im Code (&lt;code&gt;Question::QT_*&lt;/code&gt;) steht.&lt;/p&gt;</description></item><item><title>Asymmetrische Modernisierung</title><link>https://markob.de/limesurvey/asymmetrische-modernisierung/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/asymmetrische-modernisierung/</guid><description>&lt;p&gt;Wer sich mit der &lt;code&gt;composer.json&lt;/code&gt; im &lt;code&gt;develop-major&lt;/code&gt;-Branch von LimeSurvey beschäftigt, findet dort folgendes Detail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vendor-dir&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vendor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;bin-dir&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vendor/bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;platform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;php&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;8.1.29&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Gleichzeitig fehlt eine explizite PHP-Anforderung:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;require&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;php&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;=8.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Auf den ersten Blick ein kleines technisches Detail. Tatsächlich illustriert diese Konstellation ein Muster, das man in jedem langlebigen PHP-System findet – und das erklärungsbedürftiger ist als es aussieht.&lt;/p&gt;
&lt;h2 id="was-configplatformphp-sagt"&gt;Was &lt;code&gt;config.platform.php&lt;/code&gt; sagt&lt;/h2&gt;
&lt;p&gt;Die &lt;code&gt;platform&lt;/code&gt;-Konfiguration sorgt dafür, dass Composer die Dependency-Auflösung gegen eine festgelegte PHP-Version durchführt – unabhängig davon was tatsächlich auf dem System läuft. Reproduzierbare Builds, konsistente Paketversionen, keine Überraschungen zwischen Entwicklungs- und Produktionsumgebung. Das ist vernünftig.&lt;/p&gt;</description></item><item><title>20 Jahre LimeSurvey – eine Entwicklungsgeschichte in drei Phasen</title><link>https://markob.de/limesurvey/20-jahre-limesurvey/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/20-jahre-limesurvey/</guid><description>&lt;p&gt;Die Release Notes von LimeSurvey sind öffentlich zugänglich. Sie beginnen mit &lt;code&gt;Changes from 0.98 to 0.99&lt;/code&gt; und enden – Stand heute – bei 7.0.0-RC1 vom 30. April 2026. Über 11.000 Zeilen, die niemand vollständig liest – und die zusammen eine Geschichte erzählen, die so komprimiert nirgendwo steht.&lt;/p&gt;
&lt;p&gt;Diese Geschichte hat drei Phasen. Sie erklärt, warum LimeSurvey so ist wie es ist – und was LimeSurvey 7 jetzt konkret bedeutet.&lt;/p&gt;
&lt;h2 id="phase-1-der-aufbau-099-bis-190-20062012"&gt;Phase 1: Der Aufbau (0.99 bis ~1.90, 2006–2012)&lt;/h2&gt;
&lt;p&gt;Die frühe Phase ist bemerkenswert dicht. Innerhalb weniger Jahre entstand das konzeptuelle Fundament, auf dem LimeSurvey bis heute steht.&lt;/p&gt;</description></item><item><title>Die LimeSurvey RemoteControl API – eine kritische Betrachtung</title><link>https://markob.de/limesurvey/remotecontrol/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/remotecontrol/</guid><description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Und sie trägt dieselben Merkmale wie der Rest des Systems.&lt;/p&gt;
&lt;h2 id="was-die-api-kann"&gt;Was die API kann&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;</description></item><item><title>Ein typisierter LimeSurvey API-Client</title><link>https://markob.de/limesurvey/remotecontrol-api/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/remotecontrol-api/</guid><description>&lt;p&gt;Im &lt;a href="https://markob.de/limesurvey-remotecontrol"&gt;vorherigen Artikel&lt;/a&gt; haben wir drei grundlegende Probleme der LSRC2-API identifiziert: ein zustandsbehaftetes Session-Modell, das der Aufrufer selbst verwalten muss, ein inkonsistentes Rückgabeformat das Erfolg und Fehler nicht unterscheidet, und einen empfohlenen Client, der LimeSurvey-Konzepte nicht kennt.&lt;/p&gt;
&lt;p&gt;Dieser Artikel zeigt, wie ein typisierter Client diese Probleme löst – nicht als vollständige Implementierung, sondern als Lösungsvorschlag anhand konkreter Beispiele.&lt;/p&gt;
&lt;h2 id="die-ausgangslage"&gt;Die Ausgangslage&lt;/h2&gt;
&lt;p&gt;Ein typischer Aufruf mit dem rohen &lt;code&gt;JsonRPCClient&lt;/code&gt; sieht so aus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;JsonRPCClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com/index.php/admin/remotecontrol&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$sessionKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;get_session_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;add_survey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sessionKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Meine Umfrage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;de&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Fehler
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Erfolg – $result ist ein int
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$surveyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;release_session_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sessionKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Drei Probleme in acht Zeilen: Der Session-Key muss manuell übergeben und am Ende freigegeben werden. Der Rückgabetyp ist unbekannt bis man ihn prüft. Und was genau in &lt;code&gt;$result['status']&lt;/code&gt; steht, weiß man erst zur Laufzeit.&lt;/p&gt;</description></item><item><title>Das LimeSurvey Plugin-Event-System – eine kritische Betrachtung</title><link>https://markob.de/limesurvey/plugin-event-kritik/</link><pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/plugin-event-kritik/</guid><description>&lt;p&gt;LimeSurvey verfügt über ein event-basiertes Plugin-System. Plugins abonnieren Events, reagieren darauf, und können Daten zurückgeben. Das Konzept ist solide – und weit verbreitet in modernen Frameworks. Die Implementierung erzählt eine andere Geschichte.&lt;/p&gt;
&lt;h2 id="was-ein-event-system-leisten-soll"&gt;Was ein Event-System leisten soll&lt;/h2&gt;
&lt;p&gt;Ein Event kommuniziert, dass etwas passiert ist oder passieren wird. Der Name beschreibt das Ereignis. Die Parameter sind bekannt und garantiert. Der Empfänger reagiert – er fragt nicht erst nach, ob die Daten überhaupt vorhanden sind.&lt;/p&gt;</description></item><item><title>newDirectRequest – vollständige Webanwendungen im LimeSurvey-Plugin</title><link>https://markob.de/limesurvey/newdirectrequest-event/</link><pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/newdirectrequest-event/</guid><description>&lt;p&gt;Der &lt;a href="https://markob.de/limesurvey-direct-event"&gt;&lt;code&gt;direct&lt;/code&gt;-Event&lt;/a&gt; ist der CLI-Einstiegspunkt für LimeSurvey-Plugins. &lt;code&gt;newDirectRequest&lt;/code&gt; ist sein HTTP-Pendant: ein Plugin wird über den Browser angesprochen, bekommt einen vollständigen HTTP-Request, und kann eine vollständige HTTP-Response zurückgeben.&lt;/p&gt;
&lt;p&gt;Was LimeSurvey dabei nicht mitliefert, ist Routing. Der Event landet im Plugin – was danach passiert, ist vollständig in der Verantwortung des Plugin-Entwicklers.&lt;/p&gt;
&lt;h2 id="wie-newdirectrequest-funktioniert"&gt;Wie &lt;code&gt;newDirectRequest&lt;/code&gt; funktioniert&lt;/h2&gt;
&lt;p&gt;Die URL, unter der ein Plugin erreichbar ist, folgt diesem Schema:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://example.com/index.php/plugins/direct/plugin/MeinPlugin
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das Plugin abonniert den Event in &lt;code&gt;init()&lt;/code&gt; und prüft das Target – analog zu &lt;code&gt;direct&lt;/code&gt;, aber case-insensitiv, da URL-Parameter in der Praxis nicht immer exakt geschrieben werden:&lt;/p&gt;</description></item><item><title>Yii commands in LimeSurvey-Plugins</title><link>https://markob.de/limesurvey/yii-commands/</link><pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/yii-commands/</guid><description>&lt;p&gt;Der &lt;a href="https://markob.de/limesurvey/direct-event"&gt;&lt;code&gt;direct&lt;/code&gt;-Event&lt;/a&gt; ist der offizielle CLI-Einstiegspunkt für LimeSurvey-Plugins – schlank, gezielt, gut für einfache Cronjobs. Aber er hat eine feste Grenze: drei Parameter, kein Routing, keine Argument-Validierung. Wer komplexere Kommandos braucht, stößt schnell an diese Grenze.&lt;/p&gt;
&lt;p&gt;Yii 1.1 bringt ein eigenes Command-System mit, das deutlich mächtiger ist. Und es lässt sich so in ein Plugin integrieren, dass die Plugin-Kapselung vollständig erhalten bleibt.&lt;/p&gt;
&lt;h2 id="was-yii-commands-können"&gt;Was Yii Commands können&lt;/h2&gt;
&lt;p&gt;Ein Yii Command ist eine Klasse, die von &lt;code&gt;CConsoleCommand&lt;/code&gt; erbt. In der einfachsten Form überschreibt sie &lt;code&gt;run()&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Der direct-Event, gezielter CLI-Einstieg für LimeSurvey-Plugins</title><link>https://markob.de/limesurvey/direct-event/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/direct-event/</guid><description>&lt;p&gt;Wenn ein Plugin nicht auf eine Benutzeraktion im Browser reagieren soll, sondern auf der Kommandozeile aufgerufen werden – für einen Cronjob, einen Datenexport, eine nächtliche Bereinigung –, dann ist &lt;code&gt;direct&lt;/code&gt; der richtige Einstiegspunkt.&lt;/p&gt;
&lt;h2 id="das-minimalbeispiel"&gt;Das Minimalbeispiel&lt;/h2&gt;
&lt;p&gt;Ein LimeSurvey-Plugin besteht aus mindestens zwei Dateien:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;upload/plugins/HelloWorld/
 config.xml
 HelloWorld.php
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Der Verzeichnisname, der Klassenname und der &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;-Eintrag in der &lt;code&gt;config.xml&lt;/code&gt; müssen identisch sein.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;config&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;metadata&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;HelloWorld&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;plugin&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;author&amp;gt;&lt;/span&gt;My Name&lt;span class="nt"&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[Hello World via CLI]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/metadata&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;compatibility&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;6.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/compatibility&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorld&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;PluginBase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LimeSurvey\PluginManager\DbStorage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HelloWorld&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HelloWorld&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;direct&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;direct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getEvent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;target&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HELLO WORLD&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nach der Aktivierung im Plugin-Manager wird das Plugin so aufgerufen:&lt;/p&gt;</description></item><item><title>LimeSurvey und Composer – eine ungewöhnliche Beziehung</title><link>https://markob.de/limesurvey/composer/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/composer/</guid><description>&lt;p&gt;Wer aus moderner PHP-Entwicklung kommt, hat bestimmte Erwartungen. Ein Projekt hat eine &lt;code&gt;composer.json&lt;/code&gt;. Der &lt;code&gt;vendor/&lt;/code&gt;-Ordner ist in &lt;code&gt;.gitignore&lt;/code&gt;. Nach einem &lt;code&gt;git clone&lt;/code&gt; folgt &lt;code&gt;composer install&lt;/code&gt; – erst dann läuft etwas. Das ist der Standard, den Composer seit über zehn Jahren etabliert hat.&lt;/p&gt;
&lt;p&gt;Bei LimeSurvey funktioniert das anders.&lt;/p&gt;
&lt;h2 id="der-erste-hinweis"&gt;Der erste Hinweis&lt;/h2&gt;
&lt;p&gt;Eine lokale LimeSurvey-Installation beginnt typischerweise so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/LimeSurvey/LimeSurvey.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Danach folgt der webbasierte Installer im Browser – Datenbankverbindung einrichten, Tabellen anlegen, Admin-Account erstellen. Fertig. Ein &lt;code&gt;composer install&lt;/code&gt; ist zu keinem Zeitpunkt nötig.&lt;/p&gt;</description></item><item><title>SurveyIndex + SurveyRuntimeHelper – Anatomie eines God Flow</title><link>https://markob.de/limesurvey/god-flow/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/god-flow/</guid><description>&lt;p&gt;Wenn zu viel Verantwortung nicht in einer Klasse steckt, sondern in einem Ablauf&lt;/p&gt;
&lt;p&gt;In den letzten Artikeln dieser Serie haben wir zwei bekannte Antipattern betrachtet: &lt;a href="https://markob.de/create-field-map-die-god-function"&gt;&lt;code&gt;createFieldMap()&lt;/code&gt;&lt;/a&gt; als Beispiel einer Funktion, die zu viele Verantwortlichkeiten in sich vereint, und &lt;a href="https://markob.de/limemailer-anatomie-einer-god-class"&gt;&lt;code&gt;LimeMailer&lt;/code&gt;&lt;/a&gt; als Beispiel einer Klasse, die zu viel weiß, zu viel hält und zu viel tut. Beide Diagnosen folgten derselben Logik: Zu viel Verantwortung an einer Stelle.&lt;/p&gt;
&lt;p&gt;Diesmal geht es um etwas anderes. Nicht eine Funktion. Nicht eine Klasse. Sondern einen Ablauf.&lt;/p&gt;</description></item><item><title>LimeSurvey neu gedacht</title><link>https://markob.de/limesurvey/neu-gedacht/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/neu-gedacht/</guid><description>&lt;p&gt;Dieser Artikel ist ein Gedankenexperiment. Er setzt voraus, was in der Realität selten gegeben ist: ausreichend Zeit, ausreichend Ressourcen und das Mandat, wirklich neu zu denken – nicht nur neu zu schreiben.&lt;/p&gt;
&lt;p&gt;Wir haben in dieser Serie viel über Geduld gesprochen. Über schrittweise Migration, über Composer Packages, über den Wert des akkumulierten Wissens. Über &lt;code&gt;createFieldMap()&lt;/code&gt; und ihre 13 Jahre alten Kommentare. Über den Expression Manager, der 15 Jahre unberührt blieb, weil ihn niemand anfassen konnte.&lt;/p&gt;</description></item><item><title>Wenn „never" die richtige Antwort ist</title><link>https://markob.de/limesurvey/wenn-never-die-richtige-antwort-ist/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/wenn-never-die-richtige-antwort-ist/</guid><description>&lt;p&gt;Im Artikel &lt;a href="https://markob.de/rewrite-reflex"&gt;Der Rewrite-Reflex&lt;/a&gt; haben wir argumentiert, warum der Impuls neu zu schreiben meistens falsch ist. Akkumuliertes Wissen geht verloren. Die neuen Entwickler treffen dieselben alten Entscheidungen – diesmal ohne den Kontext der sie damals erklärt hätte. Ein Rewrite beginnt bei null. Sauber. Modern. Ohne dieses Wissen.&lt;/p&gt;
&lt;p&gt;Im Artikel &lt;a href="https://markob.de/refactoring-kosten"&gt;Refactoring – was kostet es, was kostet es nicht?&lt;/a&gt; haben wir gesagt: &lt;em&gt;„Later braucht einen Termin.&amp;quot;&lt;/em&gt; Technische Schulden akkumulieren stille Kosten. Wer nie neu bewertet, trifft keine Entscheidung – er lässt die Entscheidung treffen.&lt;/p&gt;</description></item><item><title>Refactoring – was kostet es, was kostet es nicht?</title><link>https://markob.de/limesurvey/refactoring-kosten/</link><pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/refactoring-kosten/</guid><description>&lt;p&gt;Im &lt;a href="https://markob.de/create-field-map-die-god-function"&gt;Artikel über &lt;code&gt;createFieldMap()&lt;/code&gt;&lt;/a&gt; haben wir gesehen was eine GodFunction ist, wie sie entsteht, und wie ein Weg heraus aussehen könnte: ein sauberes Composer Package, vollständige QA-Kette, 100% Testbarkeit.&lt;/p&gt;
&lt;p&gt;Der Plan ist klar. Die Frage die danach kommt, ist schwieriger:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Was kostet das? Und was kostet es, es nicht zu tun?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Das ist keine technische Frage mehr. Das ist eine unternehmerische. Und sie landet früher oder später auf dem Tisch des CTOs oder Lead Developers – meistens dann wenn das Budget knapp ist und die Roadmap voll.&lt;/p&gt;</description></item><item><title>createFieldMap() – die God Function</title><link>https://markob.de/limesurvey/create-field-map-die-god-function/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/create-field-map-die-god-function/</guid><description>&lt;p&gt;&lt;a href="https://markob.de/limesurvey/helper-funktionen"&gt;Im Artikel über die Helper-Funktionen&lt;/a&gt; haben wir &lt;code&gt;common_helper.php&lt;/code&gt; als Ganzes betrachtet – 130+ globale Funktionen, über 11.000 Aufrufe im Code, ein Relikt aus der prozeduralen Ära. &lt;a href="https://markob.de/limemailer-anatomie-einer-god-class"&gt;In einem anderen Artikel&lt;/a&gt; haben wir &lt;code&gt;LimeMailer&lt;/code&gt; als GodClass analysiert: eine Klasse die zu viel weiß, zu viel tut, und deshalb praktisch nicht testbar ist.&lt;/p&gt;
&lt;p&gt;Heute schauen wir auf etwas das noch subtiler ist: eine &lt;strong&gt;GodFunction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Keine Klasse. Eine einzelne Funktion. Mit über 600 Zeilen Code.&lt;/p&gt;</description></item><item><title>Wann hört Legacy auf, Legacy zu sein?</title><link>https://markob.de/limesurvey/wann-hoert-legacy-auf-legacy-zu-sein/</link><pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/wann-hoert-legacy-auf-legacy-zu-sein/</guid><description>&lt;p&gt;Im Artikel &lt;a href="https://markob.de/der-rewrite-reflex"&gt;Der Rewrite-Reflex&lt;/a&gt; haben wir uns gefragt, warum Entwickler beim Anblick einer gewachsenen Codebasis sofort neu schreiben wollen – und warum das meistens ein Fehler ist. Die Alternative lautet: verstehen statt ersetzen. Schrittweise Entflechtung. Abhängigkeiten sichtbar machen. Vorhandenes Wissen bewahren.&lt;/p&gt;
&lt;p&gt;Das ist der Weg. Aber wohin führt er?&lt;/p&gt;
&lt;p&gt;Wann ist man fertig? Wann hört Legacy auf, Legacy zu sein?&lt;/p&gt;
&lt;p&gt;Ich habe lange gebraucht um zu merken, dass ich diese Frage nie wirklich gestellt hatte. Und als ich sie stellte, merkte ich: die meisten können sie nicht beantworten.&lt;/p&gt;</description></item><item><title>LimeSurvey und Yii – eine Framework-Geschichte</title><link>https://markob.de/limesurvey/yii/</link><pubDate>Sun, 12 Apr 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/yii/</guid><description>&lt;p&gt;Es gibt Entscheidungen in der Softwareentwicklung, die man bewusst trifft.&lt;br&gt;
Und es gibt Entscheidungen, die man trifft, weil man keine andere Wahl hat.&lt;br&gt;
Die Entscheidung, mit der LimeSurvey 2012 zu Yii 1.1 gewechselt hat, war die zweite Art.&lt;/p&gt;
&lt;p&gt;Aber fangen wir von vorne an.&lt;/p&gt;
&lt;h2 id="prado--der-anfang-einer-linie"&gt;Prado – der Anfang einer Linie&lt;/h2&gt;
&lt;p&gt;Im Juni 2004 veröffentlichte der chinesische Entwickler &lt;strong&gt;Qiang Xue&lt;/strong&gt; die erste Version eines PHP-Frameworks namens &lt;strong&gt;PRADO&lt;/strong&gt; – &lt;em&gt;PHP Rapid Application Development Object-oriented&lt;/em&gt;. PHP 5.0 war gerade frisch erschienen, und Zend hatte einen Wettbewerb ausgeschrieben um die neue Version zu fördern. Qiang reimplementierte Prado für PHP 5 – und gewann.&lt;/p&gt;</description></item><item><title>Wie PHPStan Legacy-Code sichtbar macht</title><link>https://markob.de/limesurvey/wie-phpstan-legacy-code-sichtbar-macht/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/wie-phpstan-legacy-code-sichtbar-macht/</guid><description>&lt;p&gt;Im &lt;a href="https://markob.de/helper-funktionen"&gt;ersten Artikel&lt;/a&gt; haben wir uns angesehen, was &lt;code&gt;Yii::app()-&amp;gt;loadHelper()&lt;/code&gt; eigentlich macht, warum die &lt;code&gt;common_helper.php&lt;/code&gt; architektonisch ein Problem ist und wie der Weg heraus aussehen könnte: Composer Autoloading, Service-Klassen, saubere Abhängigkeiten.&lt;/p&gt;
&lt;p&gt;Was wir damals nicht hatten: &lt;strong&gt;Zahlen.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wie oft wird eine Funktion aus &lt;code&gt;common_helper.php&lt;/code&gt; eigentlich aufgerufen? Welche Funktionen sind so gut wie tot und können sofort gelöscht werden? Und welche sind so tief im Code verwurzelt, dass eine Migration echte Arbeit bedeutet?&lt;/p&gt;</description></item><item><title>LimeSurvey und die Helper-Funktionen</title><link>https://markob.de/limesurvey/helper-funktionen/</link><pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/helper-funktionen/</guid><description>&lt;p&gt;Wer LimeSurvey als Plugin-Entwickler nutzt oder in der Codebasis arbeitet, begegnet früher oder später diesem Muster:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Yii&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;loadHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;common&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Yii&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;loadHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;surveytranslator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Yii&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;loadHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;replacements&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dahinter steckt ein einfacher Mechanismus: &lt;code&gt;loadHelper('common')&lt;/code&gt; lädt die Datei &lt;code&gt;application/helpers/common_helper.php&lt;/code&gt; und macht ihre Funktionen im globalen Scope verfügbar. Dasselbe Muster findet sich auch bei &lt;code&gt;loadLibrary()&lt;/code&gt;, das auf &lt;code&gt;application/libraries/&lt;/code&gt; zeigt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$helper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Yii&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application.helpers.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$helper&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_helper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$library&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Yii&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application.libraries.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$library&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der konzeptuelle Unterschied zwischen Helper und Library ist dabei in der LimeSurvey-Codebasis kaum greifbar – beide Mechanismen lösen dasselbe Problem auf dieselbe Art. Das war in einer Zeit sinnvoll, als PHP noch kein Autoloading kannte und Funktionen manuell eingebunden werden mussten. PHP 5.1 war 2005. Composer erschien 2012.&lt;/p&gt;</description></item><item><title>LimeMailer - Anatomie einer God Class</title><link>https://markob.de/limesurvey/limemailer-anatomie-einer-god-class/</link><pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/limemailer-anatomie-einer-god-class/</guid><description>&lt;p&gt;&lt;a href="https://github.com/LimeSurvey/LimeSurvey/blob/master/application/core/LimeMailer.php"&gt;LimeMailer&lt;/a&gt; ist die zentrale E-Mail-Komponente von LimeSurvey. Sie erbt von PHPMailer, der weit verbreiteten PHP-Mailer-Bibliothek, und erweitert diese um LimeSurvey-spezifische Logik: Umfrage-Templates, Token-Ersetzungen, Anhänge nach E-Mail-Typ, Plugin-Events und vieles mehr.&lt;/p&gt;
&lt;p&gt;Dieser Artikel betrachtet die Klasse nicht als Fehler ihrer Entwickler. Wer in einem laufenden Open-Source-Projekt unter Zeitdruck Features liefert, trifft Entscheidungen, die im Moment vernünftig sind. Technische Schulden entstehen nicht durch Unwissenheit, sondern durch die Realität von Softwareentwicklung. Was hier analysiert wird, ist das Muster – nicht die Person dahinter.&lt;/p&gt;</description></item><item><title>LimeSurvey und wie Antworten gespeichert werden</title><link>https://markob.de/limesurvey/datenmodell/</link><pubDate>Fri, 20 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/datenmodell/</guid><description>&lt;p&gt;Die Art, wie LimeSurvey Umfrageantworten speichert, gehört zu den ältesten und fundamentalsten Architekturentscheidungen der Plattform. Sie beeinflusst alles, was darüber liegt: Plugins, Integrationen, Reporting und die Erweiterbarkeit des Systems.&lt;/p&gt;
&lt;p&gt;Wer mit LimeSurvey arbeitet, kommt früher oder später mit dieser Struktur in Berührung – spätestens dann, wenn Antwortdaten außerhalb der Plattform ausgewertet oder in eigene Systeme integriert werden sollen.&lt;/p&gt;
&lt;h2 id="survey-spezifische-antworttabellen"&gt;Survey-spezifische Antworttabellen&lt;/h2&gt;
&lt;p&gt;Für jede Umfrage legt LimeSurvey eine eigene Datenbanktabelle an. Der Tabellenname folgt dem Schema&lt;/p&gt;</description></item><item><title>LimeSurvey und die Hungarian Notation</title><link>https://markob.de/limesurvey/hungarian-notation/</link><pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/hungarian-notation/</guid><description>&lt;p&gt;Wer schon einmal einen Blick in den Quellcode von LimeSurvey geworfen hat, dem begegnet unweigerlich Folgendes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$iSurveyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$sLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;de&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$oSurvey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Survey&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;findByPk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$iSurveyId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$aQuestions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$oSurvey&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$bActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das Präfix verrät den Typ: &lt;code&gt;i&lt;/code&gt; für Integer, &lt;code&gt;s&lt;/code&gt; für String, &lt;code&gt;o&lt;/code&gt; für Object, &lt;code&gt;a&lt;/code&gt; für Array, &lt;code&gt;b&lt;/code&gt; für Boolean. Diese Konvention nennt sich Hungarian Notation – und sie stammt aus einer Zeit, in der PHP noch keinerlei Typisierung kannte. LimeSurvey existiert seit über 20 Jahren, und manche dieser Muster sind genauso alt.&lt;/p&gt;</description></item><item><title>Der Rewrite-Reflex</title><link>https://markob.de/limesurvey/rewrite-reflex/</link><pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/rewrite-reflex/</guid><description>&lt;p&gt;Wer zum ersten Mal in eine gewachsene Codebasis eintaucht – unbekannte Strukturen, kryptische Bezeichner, Abhängigkeiten, die sich wie ein Geflecht durch das System ziehen – kennt den Impuls: &lt;strong&gt;neu schreiben.&lt;/strong&gt; Sauber. Modern. Diesmal richtig.&lt;/p&gt;
&lt;p&gt;Dieser Reflex ist verständlich. Er ist menschlich. Und er ist einer der teuersten Fehler, den ein Entwicklungsteam machen kann.&lt;/p&gt;
&lt;h2 id="was-ist-legacy-code-überhaupt"&gt;Was ist Legacy-Code überhaupt?&lt;/h2&gt;
&lt;p&gt;Bevor man über Rewrite oder Modernisierung spricht, lohnt sich ein Blick auf die Frage: &lt;strong&gt;Was ist Legacy-Code?&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>LimeSurvey - ein System im Übergang</title><link>https://markob.de/limesurvey/ein-system-im-uebergang/</link><pubDate>Sun, 01 Mar 2026 00:00:00 +0000</pubDate><guid>https://markob.de/limesurvey/ein-system-im-uebergang/</guid><description>&lt;h2 id="was-ist-limesurvey"&gt;Was ist LimeSurvey?&lt;/h2&gt;
&lt;p&gt;LimeSurvey gehört seit vielen Jahren zu den meistverbreiteten Open-Source-Umfragelösungen im PHP-Ökosystem. Die Plattform ermöglicht die Erstellung, Verwaltung und Auswertung von Online-Umfragen und richtet sich sowohl an einzelne Anwender als auch an Organisationen mit komplexen Anforderungen. Mit einer großen aktiven Installationsbasis und kontinuierlicher Weiterentwicklung ist LimeSurvey ein Projekt, das seit langem produktiv eingesetzt wird. Für Umfragen im akademischen oder organisatorischen Kontext gibt es kaum eine vergleichbare Open-Source-Alternative mit diesem Funktionsumfang.&lt;/p&gt;</description></item></channel></rss>