Plugin per dokuwiki che filtra il contenuto in base a nome utente e gruppi
Guida all'uso sulla Wiki di Dokuwiki.
È utilizzato anche in questa stessa wiki, vediamolo in azione. In questo momento
Il messaggio qui sopra cambia a seconda del login dell'utente. Ovviamente si può sempre rivelare guardando il sorgente della pagina. Lo scopo non è tenerla segreta, solo personalizzarla.
Esiste già un plugin che fa questo, si chiama IfAuth. Tuttavia permette soltanto di combinare condizioni (e.g. nome utente o gruppo di appartenenza, potenzialmente negati) con l'operatore OR, senza associatività, e quindi non è funzionalmente completo (cioè non puoi esprimere condizioni tipo “gruppo A e anche gruppo B”).
Ifauth è un plugin molto semplice, riadattarlo perché fosse funzionalmente completo sarebbe stato equivalente a riscriverlo, per cui ho deciso di riscriverlo.
Tra le ultime modifiche, è stato riparata la grammatica per supportare username con dash e dots, aggiunto il supporto per stringhe multibyte, e risistemato l'output perché non spezzi le sezioni di DokuWiki.
Un grazie va ad Andreas Gohr e ad annda che mi hanno aiutato a rendere più robusto l'output hanno ripulito la parte specifica a DokuWiki. Un doppio grazie ad Andreas per tutto il suo lavoro al progetto DokuWiki. Grazie anche a tanakakz-alpha per aver sollevato la questione delle stringhe multibyte e a Matthias Keller per aver individuato la necessità di estendere i literals.
Una possibilità era creare una sintassi semplice, concatenando espressioni letterali (utente
o @gruppo
, potenzialmente negati) con gli operatori logici AND e OR, ma personalmente non mi ricordo mai quale dei due ha la priorità sull'altro, quindi dare la possibilità di usare le parentesi era necessario.
Espressioni letterali, AND, OR, NOT e parentesi possono costruire espressioni booleane arbitrarie, per cui l'ideale sarebbe stato avere qualcosa di pratico come la libreria Asteval di Python, che costruisce AST della sintassi di Python stesso e permette di valutarli in un contesto pressoché sicuro. Tuttavia non ho trovato niente del genere per PHP, per cui l'ho implementato direttamente.
Per ottenere un AST da un'espressione arbitraria, sono necessari tokenizer, lexer e parser.
Il tokenizer è implementato utilizzando espressioni regolari (che è un po' eccessivo e sicuramente non performante, ma decisamente pratico). Consuma caratteri dall'espressione e identifica i vari token specificati nella lista dei token supportati. È in grado di ignorare token come gli spazi, e ricorda la posizione identificata nella stringa originale per dare messaggi di errore comprensibili.
Il lexer scannerizza in ordine una grammatica definita direttamente nel plugin, e supporta operatori di diverso tipo: infix, prefix, postfix, wrap (non proprio un fixing ma serve per le parentesi), e nessuno (per le espressioni letterali), con delle limitazioni nel numero di token che è in grado di identificare per ciascun tipo di operatore. Ogni operatore ammette un certo rango, con delle limitazioni.
Ad esempio, l'operatore “OR” ha un solo token, ||
, ed è un operatore infix con arietà infinita, cioè è in grado di identificare espressioni del tipo x0 || x1 || … || xn
. Le limitazioni sul numero di token e sul rango servono fondamentalmente per avere un pattern matching semplificato corrispondente al tipo di fixing dell'operatore: intercalato tra altre espressioni per prefix/postfix/infix (con l'opportuno shift di uno se si tratta di prefix e postfix), oppure intorno alle espressioni (le parentesi). Fondamentalmente, si tratta di trovare tutti i token di un certo operatore, intercalati opportunamente ad altre espressioni. Processando gli operatori in ordine di priorità, si garantisce un'espressione ben formata (oppure si è in grado di lanciare un'eccezione se ad esempio c'è il token (
ma non il corrispondente )
).
Il parser e il lexer sono integrati in uno in quest'implementazione. Non appena un operatore viene identificato, i token rilevanti per quell'operatore vengono raggruppati e sostituiti con un'istanza di tale operatore. I raggruppamenti codificano l'AST: si procede ad identificare l'operatore successivo, partendo dalla radice dell'albero e scendendo in tutti gli operatori precedentemente identificati. Il risultato è direttamente un AST.
L'AST ha una funzionalità per ricostruire l'espressione originale (a meno di spazi) che viene utilizzata nell'unit test.
L'AST può venir valutato direttamente, basta attraversarlo. Le informazioni relative all'utente e ai suoi gruppi però sono racchiuse in una classe EvaluationContext
, per poter simulare utenti e gruppi in unit test.
L'espressione è
(!@A && !B && @C) || !(@D || E || @F)
Il tokenizer la scompone in
OPEN_PAR NOT IN_GROUP <A> AND NOT <B> AND IN_GROUP <C> CLOSE_PAR OR NOT OPEN_PAR IN_GROUP <D> OR <E> OR IN_GROUP <F> CLOSE_PAR
Il lexer/parser processa in ordine espressioni letterali, sottoespressioni, operatore di test del gruppo, NOT, AND, OR, quindi in ordine trasforma l'espressione come segue:
OPEN_PAR NOT IN_GROUP [ Lit: <A> ] AND NOT [ Lit: <B> ] AND IN_GROUP [ Lit: <C> ] CLOSE_PAR OR NOT OPEN_PAR IN_GROUP [ Lit: <D> ] OR [ Lit: <E> ] OR IN_GROUP [ Lit: <F> ] CLOSE_PAR
[ Subexpr: NOT IN_GROUP [ Lit: <A> ] AND NOT [ Lit: <B> ] AND IN_GROUP [ Lit: <C> ] ] OR NOT [ Subexpr: IN_GROUP [ Lit: <D> ] OR [ Lit: <E> ] OR IN_GROUP [ Lit: <F> ] ]
[ Subexpr: NOT [ InGroup: [ Lit: <A> ] ] AND NOT [ Lit: <B> ] AND [ InGroup: [ Lit: <C> ] ] ] OR NOT [ Subexpr: [ InGroup: [ Lit: <D> ] ] OR [ Lit: <E> ] OR [ InGroup: [ Lit: <F> ] ] ]
[ Subexpr: [ Not: [ InGroup: [ Lit: <A> ] ] ] AND [ Not: [ Lit: <B> ] ] AND [ InGroup: [ Lit: <C> ] ] ] OR [ Not: [ Subexpr: [ InGroup: [ Lit: <D> ] ] OR [ Lit: <E> ] OR [ InGroup: [ Lit: <F> ] ] ] ]
[ Or: [ Subexpr: [ And: [ Not: [ InGroup: [ Lit: <A> ] ] ], [ Not: [ Lit: <B> ] ], [ InGroup: [ Lit: <C> ] ] ] ], [ Not: [ Subexpr: [ Or: [ InGroup: [ Lit: <D> ] ], [ Lit: <E> ], [ InGroup: [ Lit: <F> ] ] ] ] ] ]
che corrisponde al seguente albero, una volta che le sottoespressioni vengono dissolte:
Or +-- And: | +-- Not: | | +-- InGroup: | | +-- Lit: <A> | +-- Not: | | +-- Lit: <B> | +-- InGroup: | +-- Lit: <C> +-- Not: +-- Or: +-- InGroup: | +-- Lit: <D> +-- Lit: <E> +-- InGroup: +-- Lit: <F>