Questo è un articolo per futuri sviluppatori che potrebbero averne bisogno per la creazione di plugin personalizzati specificamente per la convalida dei campi utente personalizzati.
Scritto mentre si utilizza la versione 3.6.0.beta3-latest di Discourse (commit corrente a7326abf15), quindi se qualcosa non dovesse funzionare, potrebbe essere che qualcosa sia cambiato nel codice Core.
Il motivo principale per cui scrivo questo è l’assoluta mancanza di informazioni riguardo all’aggiunta di convalide personalizzate ai campi utente personalizzati.
La breve storia è che ho dovuto aggiungere una convalida personalizzata per uno dei campi utente personalizzati, che è fondamentalmente un campo di testo libero. Il requisito principale era rendere questo campo univoco. Essendo uno sviluppatore Python/PHP, è abbastanza facile da implementare nei framework/CMS con cui lavoro, mentre Discourse non lo era e non si tratta di specificità del linguaggio, ma del fatto che non c’è documentazione per farlo e i post che ho trovato con domande simili che potrebbero aiutare, non hanno ricevuto risposta. Dopo mezza settimana di ricerca nel Core, ho finalmente ottenuto il risultato atteso e voglio condividerlo con altri, poiché potrebbe aiutare qualcuno.
Quindi, un campo utente personalizzato che dovrebbe avere un valore univoco.
Per risolvere questo problema, dobbiamo creare un plugin personalizzato per Discourse, il motivo principale è che dobbiamo controllare il valore nel DB, cosa che viene fatta nel backend.
Ecco la struttura:
- my_custom_plugin
-- assets
--- javascripts
---- discourse
----- initializers
------ your-initializer-js-file.js
-- config
--- locales
---- server.en.yml
---- client.en.yml
--- settings.yml
-- plugin.rb
Ora il problema principale che ho affrontato è stato come iniettare nei campi personalizzati. Dopo aver cercato nel Core, ho trovato il metodo API addCustomUserFieldValidationCallback, che si trova nel file plugin-api.gjs nel Core. Questo è un metodo che viene attivato dopo il primo tentativo di invio del modulo di registrazione e successivamente viene attivato ad ogni digitazione nell’input o in qualsiasi campo utente personalizzato, che è ciò di cui avevo bisogno.
Nell’esempio per questo metodo, mostra che accetta una funzione di callback e l’argomento che ti restituisce è il userField stesso con cui stai interagendo al momento.
Ecco il codice per your-initializer-js-file.js (non completo ma sufficiente):
import { apiInitializer } from "discourse/lib/api";
import { i18n } from "discourse-i18n";
export default apiInitializer("1.8.0", (api) => {
// Elenco degli ID dei campi che richiedono una convalida univoca
const UNIQUE_FIELD_IDS = ["5"];
// Callback di convalida lato client
api.addCustomUserFieldValidationCallback((userField) => {
const fieldId = userField.field.id.toString();
// Convalida solo i campi nel nostro elenco di campi univoci
if (UNIQUE_FIELD_IDS.includes(fieldId)) {
// Controlla se il valore è vuoto
if (!userField.value || userField.value.trim() === "") {
return null; // Lascia che la convalida predefinita gestisca i valori vuoti
}
const value = userField.value.trim();
// Controlla rispetto all'elenco dei valori
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;
});
});
Punti principali da commentare qui:
- UNIQUE_FIELD_IDS viene utilizzato per filtrare quale campo voglio che sia influenzato dalla mia logica personalizzata. Il numero 5 è l’ID del campo, che può essere controllato aprendo la pagina Modifica per il campo personalizzato nell’URL.
- isValueUnique è una funzione personalizzata in cui tutta la tua logica personalizzata esegue la convalida. Dovrebbe essere specificata al di fuori di api.addCustomUserFieldValidationCallback.
Poiché dobbiamo controllare il valore nel DB, ciò significa che dobbiamo effettuare una chiamata API a un endpoint che eseguirà la convalida e ci fornirà una risposta se è valido o meno, OPPURE potrebbe esserci un altro modo per verificarlo senza un endpoint personalizzato che non ho ancora trovato. Esiste un modulo per alcune convalidi, ma la sua convalida è limitata a controlli REGEX direttamente in JS, che non è ciò di cui avevo bisogno.
Il file plugin.rb è dove specifichiamo le informazioni riguardanti il nostro plugin personalizzato, le sue configurazioni e tutto ciò che è possibile aggiungere. Nel mio caso, registro il mio endpoint personalizzato per la convalida. Ecco un esempio di possibile convalida, potresti averla diversamente.
# frozen_string_literal: true
# name: my_custom_plugin
# about: Convalida l'unicità dei campi utente personalizzati
# version: 0.1
# authors: Yan R
enabled_site_setting :my_custom_plugin_enabled
after_initialize do
# Elenco hardcoded degli ID dei campi univoci
UNIQUE_FIELD_IDS = ["5"].freeze
# Aggiungi endpoint API per la convalida
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_")
# Normalizza il valore: rimuovi spazi bianchi e confronta senza distinzione tra maiuscole e minuscole
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
Ora la parte complicata, puoi usare questo endpoint ed effettuare una chiamata fetch/ajax dal tuo initializer, ma non funzionerà, il motivo principale è che addCustomUserFieldValidationCallback non funziona con callback asincroni, quindi devi effettuare una chiamata che non sia asincrona. Ho usato XMLHttpRequest per questo, ad esempio xhr.open('GET', '/my_custom_plugin_endpoint_url/${fieldId}', true); dove true disabilita l’asincrono.
Questo inizierà a funzionare, ma incontrerai un problema, dove non puoi digitare velocemente nell’input, perché addCustomUserFieldValidationCallback viene attivato con ogni lettera fornita e attenderà finché non avrà un ritorno corretto, quindi bloccherà il tuo input.
Non l’ho mostrato negli esempi, ma la mia soluzione è stata recuperare l’elenco dei valori univoci dei campi dopo il caricamento della pagina e usarlo per filtrare il valore direttamente in JS senza effettuare chiamate API aggiuntive, devi solo assicurarti di avere una quantità ragionevole di valori e proteggere l’endpoint. Nel mio caso, invece dell’elenco dei valori, ho restituito un elenco di hash, quindi in JS trasformo il valore digitato in un hash e confronto gli hash. Oltre al fatto che è un po’ più sicuro, riduce anche significativamente il peso della risposta, quindi puoi ottenere molti più valori da confrontare.
Ecco le informazioni riguardanti la cartella di configurazione.
settings.yml:
plugins:
my_custom_plugin_enabled:
default: true
client: true
server.en.yml:
en:
site_settings:
my_custom_plugin_enabled: "Abilita la convalida dei campi utente univoci"
my_custom_plugin_text_validator:
value_taken: "Questo valore è già stato preso"
client.en.yml:
en:
js:
my_custom_plugin_text_validator:
value_taken: "Questo valore è già stato preso"
Aggiunta: Non ho trovato informazioni su come aggiungere il mio plugin personalizzato oltre ad aggiungerlo in app.yml, poiché ho una configurazione Docker. Quello che ho fatto è stato aggiungere il mio plugin personalizzato nella cartella/repository docker, che contiene la configurazione di Discourse e l’ho montato durante l’operazione di Ricompilazione nella cartella dei plugin all’interno del Container.
Spero che questo aiuti qualcuno a implementare convalide personalizzate per i campi utente.