Von allen Projekten, an denen ich in den letzten Jahren gearbeitet habe, sticht eines als mein Favorit hervor: Ich habe ein WordPress-Plugin namens Great Eagle (Tolkien-Referenz) geschrieben, das es meinem Team ermöglicht, Themes und Plugins von unseren privaten Bitbucket-Repos über die normale wp-admin Updates-Oberfläche zu installieren und zu aktualisieren.
Dieses Plugin hat unseren Entwicklungsbetrieb in Bezug auf Best Practices der Entwicklung auf ein neues Level gehoben, auf Arten, die wir nie erwartet oder beabsichtigt hatten. Es zwingt uns, korrekte Versionsnummern zu verwenden, da wir ohne sie nicht mehr deployen können. Es zwingt uns, unsere Arbeit in Bitbucket zu speichern, da wir ohne sie nicht mehr deployen können. Es zwingt uns, die Kommandozeile auf dem Weg zum Deployment unserer Arbeit zu nutzen (womit ich einfach nur meine, git push origin master, meine), was uns dann dazu brachte, phpUnit zu verwenden. Jetzt können wir nicht mehr deployen, es sei denn, unsere Tests sind erfolgreich. Wir haben das Nirvana der testgetriebenen Entwicklung erreicht, alles nur, weil wir mit dem irrelevanten Schritt des Deployments von Git begonnen haben.
Wenn das alles standardmäßig und offensichtlich klingt, großartig. Ich würde mich freuen, von Ihnen zu lernen. Wenn das wie exotisches Rigmarol klingt, wissen Sie was? Dieser Artikel ist für Sie.
Haftungsausschluss: Meine Arbeit an diesem Plugin ist stark beeinflusst von, und in einigen Fällen plagiiert von, dem ausgezeichneten GitHub Updater Plugin von Andy Fragen. Der Grund, warum ich mein eigenes geschrieben habe, ist, dass wir Hunderte von Themes und Plugins in Bitbucket haben und ich einige Skalierungsprobleme hatte, als ich GHU testete, die seither behoben wurden. Wahrscheinlich habe ich zu früh aufgegeben, da dieses Plugin seit Jahren aktiv und fachmännisch entwickelt wird. Mehr als alles andere wollten wir einfach eine Version, die wir komplett selbst warten. Ich werde einige Gists aus meinem Plugin vorstellen, aber letztendlich **empfehle ich den Benutzern, sich an GHU zu halten**, da es wahrscheinlich besser für die meisten Leute geeignet ist und ich auch nicht die Dynamik dieses großartigen Projekts schmälern möchte.
Voraussetzungen
Meine Beispiele zeigen eine Multisite-Installation, aber das ist nicht besonders wichtig. Dies funktioniert auch auf einer Single-Site-Installation einwandfrei. Ich verwende derzeit die WordPress-Version 4.8-alpha-39626, aber auch das ist nicht besonders wichtig.
Von größter Bedeutung ist meine Annahme, dass alle Themes und Plugins an Ihrem Arbeitsplatz jeweils in ihrem eigenen Bitbucket-Repo gespeichert sind. Das ist eine ziemliche Annahme! Kein Witz: Als wir damit begannen, stellten wir ein Unternehmen ein, das manuell ein Repo für jedes unserer Themes und Plugins erstellte. Zuvor haben wir SVN (schlecht!) verwendet.
Wie funktioniert es?
Es gibt drei (ungefähre) Schritte
1) Erstellen Sie eine Benutzeroberfläche für den Benutzer, um eine API-Anfrage an Bitbucket zu stellen und alle unsere Repository-Daten in die WordPress-Datenbank zu spiegeln. Nicht alle Daten über jedes Repo, wirklich nur der Slug-Name, den wir als Schlüssel für tiefere Abfragen verwenden.

Eine Alternative wäre, dies automatisch zu erstellen, sobald es leer ist, aber vorerst bin ich glücklich, die vollständige Kontrolle darüber zu haben, wann eine so große Anzahl von API-Anfragen ausgeführt wird.
2) Sobald wir ein paar Informationen über alle unsere Repos gespiegelt haben, können wir ein jQuery-Autocomplete anbieten, um einige Repos für die Datenanalyse auszuwählen, wobei wir mehrere weitere API-Aufrufe für jedes einzelne durchführen, um Zugriff auf tiefere Informationen wie Versionsnummer und Download-URL zu erhalten.

Warum nicht sofort alle diese Details für alle Repos erfassen? Weil wir Hunderte von Repos haben und es mehrere Aufrufe pro Repo braucht, um alle relevanten Informationen wie die Versionsnummer zu erfassen. Das würde wahrscheinlich 15-30 Minuten und über 1.000 API-Reisen dauern.
3) Sobald wir detaillierte Informationen über die Handvoll Repos haben, die wir im Moment nutzen möchten, können wir zwei wichtige Dinge darüber feststellen. Erstens: Ist es in WordPress installiert? Wenn nicht, wird es in einer Benutzeroberfläche angezeigt, damit wir es installieren können. Zweitens: Wenn es installiert ist, ist es auf der neuesten Version? Wenn nicht, wird es in der normalen wp-admin Updates-Oberfläche angezeigt.



Falls ein Repo nicht lesbar ist (vielleicht fehlen ihm die richtigen Docblocks oder Namenskonventionen), wird es aus all diesen Schritten ausgeschlossen. Dies ist uns nur bei einer kleinen Handvoll schlecht benannter Plugins passiert, aber es kann ärgerlich sein, da die Änderung der Plugin-Ordner- und Dateinamen das Plugin deaktivieren kann.
Hoppla. Wie funktioniert das genau?
Gute Frage. Ich werde erklären, was die kniffligen Teile waren und einige Codeausschnitte aus meinem Plugin teilen.
Erstellung der Liste der Repos
Die maximale Anzahl von Repos pro API-Aufruf beträgt 100. So funktioniert die Bitbucket-API. Wir haben weitaus mehr als das in unserem Konto, daher müssen wir Bitbucket in einer Schleife aufrufen.
<?php
/**
* Store a "shallow" list of repos.
*/
public function set_repo_list() {
...
// Let's get 100 per page, which is the maximum.
$max_pagelen = 100;
....
// Get the first page of repos.
$page = 1;
$call = new LXB_GE_Call( 'api', "repositories/$team", $max_pagelen, $page );
$get = $call -> get();
$out = $get['values'];
// Now we know how many there are in total.
$total = $get['size'];
// How many pages does that make for?
$num_pages = ceil( $total / $max_pagelen );
// Query each subsequent page. We already got the first one.
while( $page < $num_pages ) {
$page++;
$next_call = new LXB_GE_Call( 'api', "repositories/$team", $max_pagelen, $page );
$next_get = $next_call -> get();
$next_repos = $next_get['values'];
$out = array_merge( $out, $next_repos );
}
// Sort the list by most recently updated.
$out = $this -> sort( $out, 'updated_on' );
$this -> repo_list = $out;
}
Bestimmung der „Haupt“-Plugin-Datei
WordPress ist sehr unvoreingenommen, wenn es um die Benennung von Plugins geht. In den meisten Fällen enthält ein Plugin-Ordner tatsächlich genau ein Plugin, und dieses Plugin hat eine „Haupt“-Datei, die einen Docblock enthält, um den Plugin-Namen, die Beschreibung, den Autor und vor allem die Versionsnummer zu vermitteln. Da diese Datei beliebigen Namen tragen kann, ist die Bestimmung, welche Datei die Haupt-Plugin-Datei ist, eine offene Frage. Der Ansatz, den ich gewählt habe, ist die Annahme, dass das Plugin einigen Namenskonventionen entspricht, die wir in unserer Arbeit zu verwenden versuchen.
<?php
function set_main_file_name() {
// Grab the slug name for this Bitbucket repo.
$slug = $this -> slug;
// Grab the list of file names in this repo.
$file_list = $this -> file_list;
// There's a good chance that there is a file with the same name as the repo.
if( in_array( "$slug.php", $file_list ) ) {
$main_file_name = "$slug.php";
// If not, there's a good chance there's a plugin.php file.
} elseif( in_array( 'plugin.php', $file_list ) ) {
$main_file_name = 'plugin.php';
// If not, it's probably a theme.
} elseif( in_array( 'style.css', $file_list ) && in_array( 'functions.php', $file_list ) ) {
$main_file_name = 'style.css';
// Else, oh well, couldn't find it.
} else {
$error = sprintf( esc_html__( 'Could not identify a main file for repo %s.', 'bucketpress' ), $slug );
$main_file_name = new BP_Error( __CLASS__, __FUNCTION__, __LINE__, func_get_args(), $error );
}
$this -> main_file_name = $main_file_name;
}
Bestimmung der Versionsnummer
Gegeben der Haupt-Plugin- oder Theme-Datei können wir in den Docblock dieser Datei eintauchen, um die Versionsnummer zu ermitteln. Hier ist, wie ich es mache.
<?php
/**
* Get the value for a docblock line.
*
* @param string $key The key for a docblock line.
* @return string The value for a docblock line.
*/
function get_value_from_docblock( $key ) {
// Grab the contents of the main file.
$main_file_body = $this -> main_file_body;
// Break the file into lines.
$lines = $this -> formatting -> get_lines_from_string( $main_file_body );
// Let's save ourselves some looping and assume the docblock is < 30 lines.
$max_lines = 30;
$i = 0;
foreach( $lines as $line ) {
$i++;
// If the line does not have the key, skip it.
if( ! stristr( $line, $key . ':' ) ) { continue; }
// We found the key!
break;
// Whoops, we made it to the end without finding the key.
if( $i == $max_lines ) { return FALSE; }
}
// Break the line into the key/value pair.
$key_value_pair = explode( ':', $line );
// Remove the key from the line.
array_shift( $key_value_pair );
// Convert the value back into a string.
$out = implode( ':', $line_arr );
$out = trim( $out );
return $out;
}
Während ich dabei bin, möchte ich die hilfreiche version_compare()-Funktion von PHP loben, die die meisten gängigen Versionssyntaxen parsen kann.
/**
* Determine if this asset needs to be updated.
*
* @return boolean Returns TRUE of the local version number
* is lower than the remote version number, else FALSE.
*/
function needs_update() {
$old_version = $this -> old_version;
$new_version = $this -> new_version;
$compare = version_compare( $old_version, $new_version );
if( $compare == -1 ) { return TRUE; }
return FALSE;
}
Parsen der readme.txt
Wir verwenden die readme.txt in unseren Plugins tatsächlich für nichts und daher parst mein Great Eagle Plugin sie auch nicht großartig. Wenn Sie jedoch Readme-Informationen integrieren möchten, empfehle ich diese Bibliothek von Ryan McCue zum Parsen.
Die Sache mit privaten Repos
Unsere Repos sind alle privat – so machen wir im Moment eben Geschäfte. Um sie abzufragen, müssen wir Anmeldeinformationen einbinden. In diesem Beispiel mache ich das über Basic Auth.
<?php
/**
* Authenticate all of our calls to Bitbucket, so that we can access private repos.
*
* @param array $args The current args for http requests.
* @param string $url The url to which the current http request is going.
* @return array $args, filtered to include BB basic auth.
*/
public function authenticate_http( $args, $url ) {
// Find out the url to Bitbucket.
$call = new LXB_GE_Call( 'web', FALSE );
$bb_url = $call -> get_url();
// If we're not calling a Bitbucket download, don't bother.
if( ! stristr( $url, $bb_url ) ) { return $args; }
if( ! stristr( $url, '.zip' ) ) { return $args; }
// Okay, time to append basic auth to the args.
$creds = $this -> creds;
$args['headers']['Authorization'] = "Basic $creds";
return $args;
}
Ich mache das über Filterung, anstatt Argumente an wp_remote_get() zu übergeben, weil ich WordPress mit diesen Anmeldeinformationen vorbereitet haben muss, wenn es seine Aufrufe während seiner normalen Theme- und Plugin-Update-Aufrufe macht, die jetzt zufällig an Bitbucket gehen.
Es wäre besser, OAuth statt Basic Auth zu verwenden, aber nach ziemlich viel Recherche bin ich zu dem Schluss gekommen, dass es keinen Weg gibt, das zu tun. Der Stolperstein liegt darin, dass der Rohinhalt der Datei zu diesem Zeitpunkt nicht Teil der Bitbucket-API ist, er wird einfach auf ihrer Website gehostet wie jede andere statische Ressource, wie zum Beispiel dieses öffentliche Test-Theme (es ist zu Demozwecken öffentlich, aber auch hier, wenn es privat wäre, könnten Sie es über Basic Auth aufrufen). Ich habe dieses bescheidene Feature-Request als Beweis meiner Bemühungen. Als Sicherheitsmaßnahme empfehle ich die Verwendung der neuen Application Passwords Funktion von Bitbucket, um ein Konto speziell und nur für skriptbasierte Aufrufe wie diesen zu erstellen, bei dem dieses App-Passwort nur Lesezugriff hat. Um es klarzustellen, mit Basic Auth gibt es ein Universum (vielleicht dieses), in dem ein Packet-Sniffing-Gegner unsere Plugin-Dateien lesen kann. Das ist für mich im Moment in Ordnung.
Hinzufügen unserer Repos zur Update-Warteschlange
Wenn es einen Schlüssel gibt, um in diesem ganzen Prozess Fuß zu fassen, dann ist es die Funktion wp_update_plugins(). Das ist eine riesige Funktion, die der Kern verwendet, um alle installierten Plugins zu durchlaufen, festzustellen, welche eine verfügbare Aktualisierung haben, und das Ergebnis in einem Transient zu speichern. Der Schlüssel ist, dass der Transient dann für Filterungen freigegeben wird, was genau das ist, was mein Plugin tut.
<?php
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'set_plugin_transient' ) );
/**
* Inject our updates into core's list of updates.
*
* @param array $transient The existing list of assets that need an update.
* @return The list of assets that need an update, filtered.
*/
public function set_plugin_transient( $transient ) {
if( ! is_array( $this -> assets_to_update ) ) { return $transient; }
foreach( $this -> assets_to_update as $asset ) {
if( empty( $asset -> transient_key ) ) { continue; }
if( ! $asset -> transient_content ) { continue; }
$transient -> response[ $asset -> transient_key ] = $asset -> transient_content;
}
return $transient;
}
Es hat mich ewig gekostet, das zu durchdringen, und es hat Monate gedauert, dieses Plugin zu schreiben. Sie sollten wahrscheinlich einfach GHU verwenden. Es ist ziemlich ähnlich. Dennoch, wenn Sie einige Dinge anpassen möchten und keine Plugins von Drittanbietern ausführen möchten, hilft Ihnen vielleicht der obige Code, Ihr eigenes zu schreiben.
Was ist also der Sinn der Sache?
Der Punkt ist nicht so sehr, *wie* man seinen eigenen Git-Deployer-Plugin baut oder welches vorhandene man verwenden sollte. Das können Sie selbst herausfinden. Das wirklich Interessante ist, zu sehen, was mit uns passiert ist, als wir angefangen haben, von Git zu deployen. Einige der Nebeneffekte waren tiefgreifend überraschend und positiv.
Auf Wiedersehen, FTP
FTP stinkt aus vielen Gründen.
- FTP-Zugriff ist ein Angriffsvektor.
- Keine einfache Möglichkeit, Änderungen zu verfolgen oder rückgängig zu machen.
- Keine einfache Möglichkeit, mehreren Personen gleichzeitig an demselben Projekt arbeiten zu lassen.
- Menschliches Versagen. Es ist ziemlich einfach, falsch zu ziehen und fallen zu lassen, was zu einem WSOD oder schlimmer führt.
- Das habe ich nie erwartet, aber es ist offensichtlich, wenn man ein Plugin auf vielen Installationen aktualisiert, dass diese Git-Methode viel schneller ist als FTP.
Mit einem Git-Deployment-System wie dem, das ich in diesem Artikel befürworte und erkläre, können Sie sogar den gesamten FTP-Zugriff auf Ihre Produktionsumgebung deaktivieren. Ernsthaft: Sie brauchen ihn nicht.
Hallo, richtige Versionierung
Ich empfehle die Verwendung eines Git-Deploy-Tools, das Docblocks verwendet, um die Versionsnummer zu ermitteln, und die Versionsnummer verwendet, um festzustellen, ob das Theme oder Plugin aktualisiert werden muss. Dies zwingt Ihr Team, ordnungsgemäße Versionsnummern zu verwenden, was ein schöner erster Schritt auf dem Weg vom schnellen Erstellen von Themes zur reifen Verwaltung einer langlebigen Codebasis ist.
Ich bin so begeistert von Unit-Tests
Wenn Sie keine Unit-Tests durchführen, wissen Sie wahrscheinlich, dass Sie es tun sollten. Mit Git-Deployment kann dies sowohl automatisch als auch zwingend erfolgen.
Wir verwenden die Kommandozeile, um unsere Arbeit von unserem lokalen MAMP nach Bitbucket zu verschieben, wie z. B. git push origin master. Jedes unserer Plugins verfügt über eine Grunt-Aufgabe, um unsere phpUnit-Tests vor einem Git-Commit auszuführen, und wenn die Tests fehlschlagen, schlägt auch der Commit fehl.
Wir binden Grunt mit GitHooks an unseren Commit und führen unsere Unit-Tests über Exec aus. Wenn die Tests fehlschlagen, schlägt auch das Deployment fehl.
Es gibt keine Möglichkeit, die Tests zu umgehen, da es keine Möglichkeit gibt, Git zum Deployen zu umgehen!
Rollbacks
Bei dieser Methode gibt es keine Rückgängigmachungen im eigentlichen Sinne. Vielmehr rollt man nur vorwärts. Was auch immer Sie reparieren oder wiederherstellen möchten, bringen Sie es in den Master, erhöhen Sie die Versionsnummer, pushen Sie und deployen Sie.
Personal
Diese Art der Reifung kann weitreichende geschäftliche Auswirkungen haben. Stellen Sie sich vor: Sie haben Nicht-Entwickler-Supportmitarbeiter an vorderster Front, die versuchen, ein Problem für einen Kunden zu debuggen. Früher hätten sie diese Anfrage in eine Entwickler-Ticket-Warteschlange stellen müssen, während der Kunde Stunden oder Tage auf eine Lösung wartet. Nicht mehr. Jetzt kann Ihr First-Line-Support-Mitarbeiter zum Netzwerkadministrator navigieren und sehen, dass auf dieser Umgebung das betreffende Plugin veraltet ist. Sie können das Plugin sofort über die normale wp-admin-Oberfläche aktualisieren. Das Ticket wird vom First-Line-Support gelöst, ohne dass das Entwicklerteam involviert ist. Vielleicht kosten diese First-Line-Mitarbeiter weniger als Entwickler, oder vielleicht verfügen sie über ein tiefes Fachwissen im Account-Management. In jedem Fall müssen Sie kein Entwicklerticket mehr eröffnen, um Updates für Ihre internen Plugins bereitzustellen. Entscheidend.
Aufstieg der Maschinen
Vor diesem Prozess waren wir ein ganz gewöhnliches Entwicklerteam, das Themes und Plugins für Kunden erstellte, per Cowboy-FTP deployte, unsere Arbeit nicht versionierte. Warum? Weil wir faul waren. Warum? Weil wir menschlich waren. Wir sind nicht mehr faul, weil wir nicht mehr menschlich sind, zumindest beim Deployen. Wir sind ein Kommandozeilen-Skript und eine Reihe von API-Anfragen, und egal wie faul wir sind, wir müssen ordnungsgemäße Deployment-Praktiken befolgen, weil wir die FTP-Anmeldeinformationen für unsere Entwickler abgeschafft haben! Darüber hinaus ist es eine schnellere Art zu deployen, frei von jeglichen Klick-und-Ziehen-Fehlern.
Können Sie das über Nacht übernehmen? Okay, nein. Es ist ein langer und teurer Prozess, und er ist *vielleicht* nichts für Sie, aber ehrlich gesagt wahrscheinlich doch. Ich denke, es gibt etwa 1.000 Entwicklungsagenturen da draußen, die dies sorgfältig in Betracht ziehen sollten.
Sind Sie sich eines Plugins bewusst, das dies tut und AWS CodeCommit unterstützt? Mein Unternehmen verwendet private CodeCommit-Repositories und wir prüfen unsere Optionen für das Deployment von benutzerdefinierten WordPress-Themes und Plugins genau so, wie Sie es in Ihrem Beitrag gezeigt haben.
Wenn Sie API-Aufrufe machen können, um die Versionsnummer und die Zip-URL Ihrer CodeCommit-Assets zusammenzustellen, ist das machbar. Sie würden sie so einbinden, wie ich es im Artikel vorschlage.
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'set_plugin_transient' ) );Scott,
Dies ist ein sehr gut geschriebener und formatierter Artikel, Hut ab für Ihre Zeit und Einblicke. Dennoch können Sie sich den Ärger sparen, Plugins zu schreiben und mit der API zu hantieren. Wir haben eine App entwickelt – Buddy.Works –, mit der Sie all diese Aktionen bei einem einzigen Push in das Bitbucket-Repository automatisieren können.
Entwickeln Sie Plugin-Module auf den Entwicklungs-/Staging-/Master-Branches
Generieren Sie CSS- und Minify-Dateien und führen Sie Unit-Tests mit Grunt aus.
Deployen Sie Plugins und Themes auf den Live-Server.
Überwachen Sie Kunden-Websites auf Ausfallzeiten und aktualisieren Sie WP/Plugins automatisch.
Erstellen Sie Backups mit WP-CLI.
Sie haben 100 % Recht bezüglich FTP, aber Sie können immer SSH-Schlüssel verwenden, um SFTP-Deployments und Skriptausführungen zu sichern.
So funktioniert es
Wir haben auch eine spezielle Anleitung für das Deployment von WordPress-Themes.
Ich würde mich freuen, wenn Sie sich das ansehen und Ihre Meinung dazu äußern würden, insbesondere da Sie ein erfahrener WP-Entwickler sind.
Nun, das sieht großartig aus! Das ist genau das, was die Branche braucht. Ihr habt das wohl gerockt. Überrascht, dass wir das noch nicht früher entdeckt haben.
Ich hätte nur kleine Einwände zu ein paar Punkten.
1) Wir aktualisieren gerne über die normale wp-admin-Oberfläche, aus ein paar Gründen. Dort werden auch alle .org-Plugins aktualisiert, die wir vielleicht verwenden, sodass wir alle unsere Updates an einem Ort durchführen können. Außerdem ist die Benutzeroberfläche jedem vertraut, der WordPress kennt.
2) Zu Ihrem Punkt: „Sparen Sie sich den Ärger, Plugins zu schreiben und mit der API zu hantieren“: Ich weiß, es kommt darauf an, wer Sie sind, aber für mich ist das, als würde man sagen: „Sparen Sie sich den Ärger eines zutiefst erfüllenden und wertvollen Lernerlebnisses und des Umgangs mit der Expertise in dieser interessanten Technologie“. Manchmal ist es eine lohnende Gelegenheit, Dinge intern zu bauen, um Teammitgliedern das Lernen zu ermöglichen! (aber nicht immer).
3) Wenn ich Ihre Definition von „Projekt“ richtig verstehe, wären wir in der Preisstufe von 300 US-Dollar pro Monat. Ich denke, das ist ein guter Preis für den angebotenen Service, aber es könnte ein paar Augenbrauen hochziehen lassen, je nachdem, wer hinschaut.
Das sind einige schwache Einwände meinerseits – Ihr Service sieht wirklich großartig aus.
Ein großartiger Einblick in einen viel fortschrittlicheren WordPress-Workflow. Danke für das Teilen!
Ich würde gerne mehr über die Tests und die von Ihnen erwähnten Hooks erfahren. Ich weiß, dass ich Tests nutzen sollte, aber ich weiß nicht, wo ich anfangen soll. Ein etablierter Workflow zu sehen, würde helfen, den Prozess zu verstehen.
Wahrscheinlich außerhalb des Rahmens dieses Artikels. Vielleicht in der Zukunft!
Vorerst empfehle ich für Tests Pippins exzellentes Tutorial
https://pippinsplugins.com/unit-tests-wordpress-plugins-introduction/
Für GitHooks nehmen Sie bitte zur Kenntnis, dass diese Arbeit mein erster Ausflug in Grunt überhaupt war. Sie können Grunt, und wenn Sie Grunt können, können Sie auch diese Dinge tun.
Gute Arbeit. Warum machen Sie daraus keine Bibliothek in jedem Plugin? Das würde Ihnen das Einstellungsmenü ersparen und ich denke, es ist besser für den Benutzer, da wir keine Plugins zur Entwicklung platzieren, um sie sichtbar zu machen…
Hallo David! Danke fürs Lesen und Kommentieren.
Ich verstehe Ihre Frage nicht ganz.
Ja, ich meine, wäre es besser als Bibliothek innerhalb des Plugins?