ProcessWire
Creare tags in ProcessWire

Creare tags con contatore in ProcessWire grazie agli hooks

Frank Vessia
06 nov 2020
Tempo di lettura: 5 minuti, 38 secondi

Un tutorial su come creare la classica nuvoletta tags con contatore automatico in ProcessWire


Sebbene un recente aggiornamento di ProcessWire abbia introdotto un nuovo metodo $page->references() che permette di ottenere tutti i riferimenti in formato pageArray di altre pagine tramite il campo "page field", questo non è sufficiente in alcuni casi, tipo quando vogliamo anche ordinare in modo decrescente i risultati.

Questa era la necessità che avevo nel gestire dei semplici tags per il mio blog nell'area backend, avrei potuto semplicemente utilizzare $tag->numReferences per sapere quante volte un tag fosse stato utilizzati all'interno del blog, ma volevo creare la classica nuvoletta e non potevo usare numReferences all'interno del selector per ordinare in modo decrescente tramite sort=-numReferences.

Con un campo numerico e due semplici hooks ho risolto il problema. Per prima cosa vediamo come ho strutturato i tags. Ogni tag è una pagina collegata all'articolo tramite un page reference a selezione multipla chiamato "tags" in modo che possa assegnare ad ogni articolo n tags, come vedi in figura. Fin qui tutto molto semplice, ma questo non mi da il numero di volte che un tag è stato usato, come detto precedentemente di potrebbe usare il numReferences in molti casi, per esempio mettiamo che nella pagina dell'articolo voglia molstrare non solo i tags corrispondenti ma anche quante altre volte il tag è stato utilizzato sul sito. Basterebbe fare così:

foreach ($page->tags as $tag){
    echo $tag->title.' - '.$tag->numReferences;
}

che molto banalmente visualizz "nometag - 2". Io però volevo creare la nuvoletta con tutti i tags del sito ordinati in modo decrescente, dal più usato al meno usato. Ho quindi dovuto creare due hooks che ad ogni salvataggio della pagina va incrementano o decrementano il contatore interno, un semplice integer che posso usare nel mio selector per l'ordinamento.

Gli hooks sono potenti strumenti di ProcessWire che permettono di intervenire in ogni metodo interno del codice sorgente e apportare modifiche e comportamenti personalizzati

Quindi ho prima creato il campo counter (integer) e lo ho assegnato al template tag, template che non aveva altri camp poichè mi serve solo gestire il nome del tag e appunto il totale degli articoli.

Successivamente, poichè avevo già un modulo per gestire altre operazioni, ho aggiunto i due hooks al suo interno, e nell'init del modulo ho chiamato le funzioni in questo modo:

public function init() {
    $this->addHookAfter('Pages::save', $this, 'updateTags');
    $this->addHookBefore('Pages::saveReady', $this, 'removeTags');
}

protected function updateTags(HookEvent $event) {
    $page = $event->arguments[0];
    if($page->template == 'article'){        
        foreach($page->tags as $t){
            $t->setAndSave('counter',$t->numReferences);
        }    
    }
}

protected function removeTags(HookEvent $event){
    $page = $event->arguments[0];
        
    if ($page->template == 'article' && $page->isChanged('tags')) {
            
         $oldPage = wire('pages')->getById($page->id, array(
            'cache' => false,
            'getFromCache' => false, 
            'getOne' => true,
         ));
            
        foreach($oldPage->tags as $t){ $oldTags[] = $t->id; }
        foreach($page->tags as $t){ $newTags[] = $t->id; }
            
        $tagsRemoved = array_diff($oldTags,$newTags);
        foreach($tagsRemoved as &$value){
            $tag = wire('pages')->get($value);
            $tag->setAndSave('counter',$tag->counter-1);
        }
    }      
}

Analizziamo il codice

Il primo hook si scatena dopo il salvataggio della pagina quando i dati sono già nel database e chiama la funzione updateTags. Questa funzione non fa altro che verificare che la pagina corrente sia un articolo e nel caso, cicla tutti i tags presenti e aggiorna il conteggio totale impostando il campo counter con il numReferences, quindi con il totale di tutte le pagine in cui è stato utilizzato. Da notare il setAndSave che è un metodo abbreviato per salvare i campi di una pagina.

Di per se solo questa piccola funzione è sufficiente a tenere sempre aggiornato il conteggio dei tags, il nostro campo counter sarà sempre uguale al campo numReferences però cunter lo possiamo usare all'interno di un selectore quindi possiamo visualizzare graficamente i nostri tags con la nuvoletta o con un indicatore numerico.

Però abbiamo un altro problema da risolvere, cosa succede se da un pagina rimuovo un tag? Beh, al momento non succede nulla e il conteggio rimane invariato, quando invece deve decrescere. Per risolvere questo problema ci serve un'altra funzione un pò più complessa, chiamata da un altro hook.

In questo caso agisco sulle pagine sul metodo saveReady, cioè quando il ProcessWire è pronto per salvare i dati, nel momento antecedente la chiamata (addHookBefore), quindi questo hook verrà eseguito ogni qualvolta, dopo aver cliccato sul bottone salva, ProcessWire sta per salvare i nuovi dati della pagina. Questo perchè ho bisogno di accedere sia ai vecchi tags che ai nuovi per calcolare l'eventuale differenza di tags e diminuire il counter di conseguenza.

Quindi chiamo la funzione removeTags che prende la pagina corrente, verifica che la pagina abbia il template article e che i tags siano affettivamente cambiati dall'ultimo salvataggio.

Tag cloud in amministrazione
Il tag cloud come appare nell'amministrazione

Come dicevo prima, poichè ci troviamo in un punto in cui la pagina non è ancora stata salvata (saveReady) è possibile accedere ancora ai vecchi dati, quindi recupero la stessa pagina passando tre parametri, di cui i primi due fondamentali allo scopo, ovvero cache => false (non cachare i dati) e getFromCache => false (non prendere i dati dalla cache) per ottenere i tags prima della modifica corrente.

Successivamente non faccio altro che trasformare i tags (vecchi e nuovi) in due array per poter usare la funzione del php array_diff, che troverà eventuali differenze. Se quindi ho eliminato un tag dalla pagina, questo mi restituirà un array con l'id del tag, nella variabile $tagsRemoved. A questo punto non mi resta che ciclare su tutti i tags rimossi e decrementare di una unità il totale.

Se non sapessi come creare un modulo c'è un modo più semplice e veloce per ottenere lo stesso risultato, ovvero creando un file php dentro /site/init.php e scrivendo gli hooks in questo modo:

$pages->addHookAfter('save', function($event) {
    $page = $event->object;
    if($page->template == 'article'){        
        foreach($page->tags as $t){
            $t->setAndSave('counter',$t->numReferences);
        }    
    }

});


$pages->addHookAfter('saveReady', function($event) {
    $page = $event->object;
    if ($page->template == 'article' && $page->isChanged('tags')) {
            
         $oldPage = $pages->getById($page->id, array(
            'cache' => false,
            'getFromCache' => false, 
            'getOne' => true,
         ));
            
        foreach($oldPage->tags as $t){ $oldTags[] = $t->id; }
        foreach($page->tags as $t){ $newTags[] = $t->id; }
            
        $tagsRemoved = array_diff($oldTags,$newTags);
        foreach($tagsRemoved as &$value){
            $tag = wire('pages')->get($value);
            $tag->setAndSave('counter',$tag->counter-1);
        }
    }  
});

Conclusioni


Questo tutorial ha anche lo scopo di dimostrarti come sia possibile intervenire facilmente all'iterno del core di ProcessWire per agire su comportamenti specifici, in questo caso il salvataggio di una pagina nel pannello di amministrazione. Gli hooks sono molto potenti e coprono quasiasi esigenza tu possa avere.