Benutzerdefinierte Felder Validierung Implementierung

Dies ist ein Artikel für zukünftige Entwickler, die ihn möglicherweise für die Erstellung benutzerdefinierter Plugins speziell für die Validierung benutzerdefinierter Benutzerfelder benötigen.

Geschrieben unter Berücksichtigung der Discourse-Version 3.6.0.beta3-latest (aktueller Commit a7326abf15), daher könnte etwas nicht funktionieren, wenn sich etwas im Kerncode geändert hat.

Der Hauptgrund für das Schreiben dieses Artikels ist das absolute Fehlen von Informationen zur Hinzufügung benutzerdefinierter Validierungen zu benutzerdefinierten Benutzerfeldern.

Die Kurzgeschichte ist, dass ich eine benutzerdefinierte Validierung für eines der benutzerdefinierten Benutzerfelder hinzufügen musste, bei dem es sich im Grunde um eine Freitext-Eingabe handelt. Die Hauptanforderung war, dass dieses Feld einen eindeutigen Wert haben sollte. Als Python/PHP-Entwickler ist dies bei den Frameworks/CMS, mit denen ich arbeite, ziemlich einfach zu implementieren, während Discourse nicht der Fall war und es nicht um sprachspezifische Details geht, sondern um die Tatsache, dass es keine Dokumentation dafür gibt und die Beiträge, die ich mit ähnlichen Fragen gefunden habe, die helfen könnten, unbeantwortet blieben. Nach einer halben Woche Kernrecherche habe ich endlich das erwartete Ergebnis erzielt und möchte es mit anderen teilen, da es jemandem helfen könnte.

Daher sollte ein benutzerdefiniertes Benutzerfeld einen eindeutigen Wert haben.

Um dies zu lösen, müssen wir ein benutzerdefiniertes Plugin für Discourse erstellen. Der Hauptgrund dafür ist, dass wir den Wert in der Datenbank überprüfen müssen, was im Backend geschieht.

Hier ist die Struktur:

- my_custom_plugin
-- assets
--- javascripts
---- discourse
----- initializers
------ your-initializer-js-file.js
-- config
--- locales
---- server.en.yml
---- client.en.yml
--- settings.yml
-- plugin.rb

Nun war das Hauptproblem, wie ich überhaupt in die benutzerdefinierten Felder eindringen kann. Nach der Suche im Kern habe ich die API-Methode addCustomUserFieldValidationCallback gefunden. Sie befindet sich in der Datei plugin-api.gjs im Kern. Dies ist eine Methode, die nach dem ersten Versuch des Absendens des Anmeldeformulars ausgelöst wird und danach bei jeder Eingabe im Feld oder in einem beliebigen benutzerdefinierten Benutzerfeld ausgelöst wird, was ich brauchte.
Im Beispiel für diese Methode wird gezeigt, dass sie eine Callback-Funktion akzeptiert und das Argument, das sie Ihnen zurückgibt, das userField selbst ist, mit dem Sie gerade interagieren.
Hier ist der Code für your-initializer-js-file.js (nicht vollständig, aber ausreichend):

import { apiInitializer } from "discourse/lib/api";
import { i18n } from "discourse-i18n";

export default apiInitializer("1.8.0", (api) => {
  // Liste der Feld-IDs, die eine eindeutige Validierung erfordern
  const UNIQUE_FIELD_IDS = ["5"];

  // Clientseitiger Validierungs-Callback
  api.addCustomUserFieldValidationCallback((userField) => {
    const fieldId = userField.field.id.toString();

    // Nur Felder in unserer Liste eindeutiger Felder validieren
    if (UNIQUE_FIELD_IDS.includes(fieldId)) {
      // Prüfen, ob der Wert leer ist
      if (!userField.value || userField.value.trim() === "") {
        return null; // Standardvalidierung für leere Werte zulassen
      }

      const value = userField.value.trim();

      // Gegen die Liste der Werte prüfen
      const unique = isValueUnique(fieldId, value);
      if (!unique) {
        return {
          failed: true,
          ok: false,
          reason: i18n("js.my_custom_plugin_text_validator.value_taken"),
          element: userField.field.element,
        };
      }
    }

    return null;
  });
});

Wichtige Punkte hierzu:

  • UNIQUE_FIELD_IDS wird verwendet, um zu filtern, welches Feld von meiner benutzerdefinierten Logik betroffen sein soll. Die Zahl 5 ist die ID des Feldes, sie kann beim Öffnen der Bearbeitungsseite für das benutzerdefinierte Feld in der URL überprüft werden.
  • isValueUnique ist eine benutzerdefinierte Funktion, in der Ihre gesamte benutzerdefinierte Logik die Validierung durchführt. Sie sollte außerhalb von api.addCustomUserFieldValidationCallback definiert werden.

Da wir den Wert in der Datenbank prüfen müssen, bedeutet dies, dass wir entweder einen API-Aufruf an einen Endpunkt machen müssen, der die Validierung durchführt und uns eine Antwort liefert, ob er gültig ist oder nicht, ODER es gibt möglicherweise eine andere Möglichkeit, dies ohne einen benutzerdefinierten Endpunkt zu überprüfen, die ich noch nicht gefunden habe. Es gibt ein Modul für einige Validierungen, aber seine Validierung ist auf REGEX-Prüfungen direkt in JavaScript beschränkt, was nicht das war, was ich brauchte.

Die Datei plugin.rb ist dort, wo wir Informationen über unser benutzerdefiniertes Plugin, seine Konfigurationen und alles Mögliche, was hinzugefügt werden kann, angeben. In meinem Fall registriere ich meinen benutzerdefinierten Endpunkt für die Validierung. Hier ist ein Beispiel für eine mögliche Validierung, Sie können sie anders haben.

# frozen_string_literal: true

# name: my_custom_plugin
# about: Validates uniqueness of custom user fields
# version: 0.1
# authors: Yan R

enabled_site_setting :my_custom_plugin_enabled

after_initialize do
  # Fest codierte Liste eindeutiger Feld-IDs
  UNIQUE_FIELD_IDS = ["5"].freeze

  # API-Endpunkte für die Validierung hinzufügen
  Discourse::Application.routes.append do
    get "/my_custom_plugin_endpoint_url/:field_id" => "my_custom_plugin_validation#validate"
  end

  class ::MyCustomPluginValidationController < ::ApplicationController
    skip_before_action :verify_authenticity_token
    skip_before_action :check_xhr
    skip_before_action :redirect_to_login_if_required

    def validate
      field_name = params[:field_name]
      field_value = params[:field_value]

      return render json: { valid: true } unless field_name.present? && field_value.present?
      return render json: { valid: true } unless field_name.start_with?("user_field_")

      # Normalisieren des Werts: Leerzeichen entfernen und case-insensitiv vergleichen
      normalized_value = field_value.to_s.strip
      return render json: { valid: true } if normalized_value.empty?

      existing = UserCustomField
        .where(name: field_name)
        .where("LOWER(TRIM(value)) = LOWER(?)", normalized_value)
        .exists?

      render json: { valid: !existing }
    end
  end
end

Nun kommt der knifflige Teil: Sie können diesen Endpunkt verwenden und einen Fetch/Ajax-Aufruf von Ihrem Initialisierer aus tätigen, aber er wird nicht funktionieren. Der Hauptgrund ist, dass addCustomUserFieldValidationCallback nicht mit asynchronen Callbacks funktioniert. Sie müssen also einen Aufruf tätigen, der nicht asynchron ist. Ich habe dafür XMLHttpRequest verwendet, als Beispiel xhr.open('GET', '/my_custom_plugin_endpoint_url/${fieldId}', true); wobei true asynchron deaktiviert.
Dies wird funktionieren, aber Sie werden auf ein Problem stoßen, bei dem Sie nicht schnell in das Feld eingeben können, da addCustomUserFieldValidationCallback bei jedem eingegebenen Buchstaben ausgelöst wird und darauf wartet, eine ordnungsgemäße Rückgabe zu erhalten, sodass es Ihre Eingabe einfriert.

Ich habe es in den Beispielen nicht gezeigt, aber meine Lösung bestand darin, die Liste der eindeutigen Feldwerte nach dem Laden der Seite abzurufen und sie direkt in JavaScript zu verwenden, um den Wert zu filtern, ohne zusätzliche API-Aufrufe zu tätigen. Sie müssen nur sicherstellen, dass Sie eine angemessene Anzahl von Werten haben und den Endpunkt sichern. In meinem Fall habe ich anstelle der Werte-Liste Hash-Listen zurückgegeben, sodass ich in JavaScript den eingegebenen Wert in einen Hash umwandle und die Hashes vergleiche. Abgesehen davon, dass es etwas sicherer ist, reduziert es auch erheblich das Antwortgewicht, sodass Sie viel mehr Werte zum Vergleichen abrufen können.

Hier sind die Informationen zum Konfigurationsordner.

settings.yml:

plugins:
  my_custom_plugin_enabled:
    default: true
    client: true

server.en.yml

en:
  site_settings:
    my_custom_plugin_enabled: "Enable unique user fields validation"
  my_custom_plugin_text_validator:
    value_taken: "This value is already taken"

client.en.yml

en:
  js:
    my_custom_plugin_text_validator:
      value_taken: "This value is already taken"

Zusatz: Ich habe keine Informationen gefunden, wie ich mein benutzerdefiniertes Plugin hinzufügen kann, außer es in app.yml hinzuzufügen, da ich ein Docker-Setup habe. Was ich getan habe, ist, mein benutzerdefiniertes Plugin im Docker-Ordner/Repository hinzuzufügen, das das Setup von Discourse enthält, und es während des Rebuild-Vorgangs in den Plugin-Ordner im Container einzubinden.

Ich hoffe, dies hilft jemandem bei der Implementierung benutzerdefinierter Validierungen für Benutzerfelder.

1 „Gefällt mir“