Durante un intervento su un sito WordPress sviluppato con MotoPress Hotel Booking, mi sono trovata davanti a un problema particolare: il plugin veniva utilizzato per gestire sia le camere di un hotel sia le sale di un centro congressi.
Dal punto di vista delle prenotazioni la soluzione aveva senso. MotoPress permetteva di gestire entrambe le tipologie di risorse attraverso lo stesso sistema. Il problema emergeva nella struttura del sito e nel modo in cui le diverse risorse venivano presentate ai motori di ricerca.
Le camere e le sale congressi condividevano infatti lo stesso custom post type e la stessa struttura URL:
/accommodation/camera-matrimoniale//accommodation/sala-congressi/
Anche Google sembrava confondere le sale del centro congressi con le camere dell’hotel.
Serviva una soluzione che permettesse di mantenere MotoPress, distinguere le due tipologie di risorse e gestire correttamente anche le pagine tradotte in inglese.
Di cosa parlerò in questa pagina
Perché MotoPress utilizza gli stessi URL per camere e sale congressi
MotoPress Hotel Booking registra le tipologie di alloggio attraverso il custom post type:
mphb_room_type
Le categorie associate a queste risorse utilizzano invece la tassonomia:
mphb_room_type_category
Questo significa che una camera matrimoniale e una sala congressi possono essere due contenuti dello stesso custom post type, anche quando appartengono a categorie completamente diverse.
MotoPress assegna normalmente alle tipologie di alloggio una struttura permalink che utilizza la base /accommodation/.
La categoria assegnata al contenuto non modifica automaticamente questa base.
Nel mio caso avevo già una categoria dedicata alle sale congressi, identificata dallo slug congressi.
L’obiettivo era quindi ottenere una struttura di questo tipo:
| Contenuto | URL desiderato |
|---|---|
| Camera matrimoniale | /accommodation/camera-matrimoniale/ |
| Suite | /accommodation/suite/ |
| Sala congressi | /centro-congressi/sala-congressi/ |
| Sala riunioni | /centro-congressi/sala-riunioni/ |
La modifica doveva riguardare esclusivamente i contenuti appartenenti alla categoria Congressi.
Prima strada: modificare lo slug globale di MotoPress
MotoPress documenta la possibilità di cambiare lo slug predefinito del custom post type attraverso il filtro register_post_type_args.
Un esempio è questo:
add_filter('register_post_type_args', function($args, $post_type) {
if ($post_type === 'mphb_room_type') {
$args['rewrite']['slug'] = 'centro-congressi';
}
return $args;
}, 10, 2);
Il codice modifica la base URL del custom post type.
Il limite è che la modifica riguarda tutte le tipologie di alloggio, non soltanto quelle appartenenti alla categoria Congressi.
Di conseguenza, anche le camere dell’hotel utilizzerebbero /centro-congressi/.
Era quindi un approccio inadatto al risultato che volevo ottenere.
Non avevo bisogno di cambiare globalmente lo slug di MotoPress. Dovevo applicare una regola condizionale in base alla categoria del singolo contenuto.
Seconda strada: modificare il permalink con post_type_link
WordPress mette a disposizione il filtro post_type_link, che permette di personalizzare l’URL generato per un contenuto appartenente a un custom post type.
La prima versione dello script utilizzava proprio questo filtro.
add_filter('post_type_link', 'custom_mphb_permalink', 10, 2);
function custom_mphb_permalink($permalink, $post) {
if ($post->post_type !== 'mphb_room_type') {
return $permalink;
}
if (has_term(
'congressi',
'mphb_room_type_category',
$post->ID
)) {
return home_url(
'centro-congressi/' . $post->post_name . '/'
);
}
return $permalink;
}
Il codice riconosce le sale congressi e restituisce il permalink desiderato.
C’è però una distinzione fondamentale nel funzionamento di WordPress: generare un nuovo URL non significa necessariamente renderlo raggiungibile.
post_type_link modifica il collegamento restituito da WordPress, ma non registra automaticamente una regola per interpretare le richieste che arrivano a quel percorso.
Se WordPress non riconosce /centro-congressi/sala-congressi/, la pagina può restituire un errore 404.
Per risolvere serviva quindi intervenire anche sul sistema di rewrite.
La soluzione: permalink condizionali e rewrite rules
L’approccio completo combina tre operazioni:
- Verificare se il contenuto MotoPress appartiene alla categoria Congressi.
- Generare il permalink personalizzato.
- Registrare una rewrite rule affinché WordPress riconosca il nuovo indirizzo.
A queste ho aggiunto il redirect 301 dal vecchio URL al nuovo, necessario per gestire i collegamenti già presenti sul sito e quelli eventualmente indicizzati da Google.
1. Riconoscere le sale congressi
Ho isolato il controllo della categoria in una funzione riutilizzabile.
function custom_mphb_is_congress($post_id) {
$categories = [
'congressi',
'congress',
'conference-rooms'
];
$terms = wp_get_post_terms(
$post_id,
'mphb_room_type_category',
['fields' => 'slugs']
);
if (is_wp_error($terms) || empty($terms)) {
return false;
}
return !empty(
array_intersect($categories, $terms)
);
}
La funzione verifica gli slug delle categorie assegnate alla tipologia di alloggio.
Ho previsto anche gli slug inglesi, perché nel sito le sale congressi erano presenti in più lingue. I valori devono naturalmente corrispondere alle categorie realmente configurate in WordPress.
Questa funzione permette di centralizzare la distinzione tra camere e sale congressi, evitando di ripetere lo stesso controllo in più script.
2. Modificare i permalink senza toccare le camere
La funzione precedente viene richiamata dal filtro post_type_link.
add_filter('post_type_link', 'custom_mphb_congress_url', 99, 2);
function custom_mphb_congress_url($permalink, $post) {
if ($post->post_type !== 'mphb_room_type') {
return $permalink;
}
if (!custom_mphb_is_congress($post->ID)) {
return $permalink;
}
return home_url(
user_trailingslashit(
'centro-congressi/' . $post->post_name
)
);
}
Con questa logica le camere conservano il permalink originale, mentre le sale congressi utilizzano /centro-congressi/.
3. Aggiungere la rewrite rule
Per permettere a WordPress di interpretare il nuovo URL ho utilizzato add_rewrite_rule().
add_action('init', function() {
add_rewrite_rule(
'^centro-congressi/([^/]+)/?$',
'index.php?post_type=mphb_room_type&name=$matches[1]',
'top'
);
}, 20);
Questa regola intercetta gli URL che iniziano con /centro-congressi/ e comunica a WordPress di cercare un contenuto del custom post type mphb_room_type con lo slug indicato.
La regola deve essere adattata se il sito utilizza una configurazione multilingua che modifica l’interpretazione degli URL.
Dopo aver aggiunto il codice è necessario salvare nuovamente i permalink da Impostazioni → Permalink, anche senza apportare modifiche.
Questo aggiornamento permette a WordPress di rigenerare le regole di riscrittura.
4. Gestire i vecchi URL con redirect 301
Modificando la struttura dei permalink, gli URL precedenti possono continuare a essere presenti nei motori di ricerca, nei link esterni e nei preferiti degli utenti.
Per evitare che rimangano due indirizzi differenti per la stessa risorsa, ho previsto un redirect permanente verso il permalink canonico.
add_action('template_redirect', function() {
if (!is_singular('mphb_room_type') || is_preview()) {
return;
}
$post_id = get_queried_object_id();
if (!custom_mphb_is_congress($post_id)) {
return;
}
$correct_url = get_permalink($post_id);
$current_path = wp_parse_url(
$_SERVER['REQUEST_URI'] ?? '',
PHP_URL_PATH
);
$correct_path = wp_parse_url(
$correct_url,
PHP_URL_PATH
);
if (
untrailingslashit((string) $current_path) !==
untrailingslashit((string) $correct_path)
) {
wp_safe_redirect($correct_url, 301);
exit;
}
}, 1);
Questo esempio gestisce il confronto dei percorsi sullo stesso dominio. Per configurazioni multilingua con domini differenti occorre gestire anche host e query string.
Il redirect 301 permette di segnalare lo spostamento permanente della pagina e riduce il rischio di lasciare raggiungibili due versioni dello stesso contenuto.
La complicazione successiva: gestire anche la lingua inglese
Una volta definita la modifica per le pagine italiane, era necessario estenderla alla versione inglese del sito.
La struttura desiderata diventava:
| Lingua | URL |
|---|---|
| IT | /centro-congressi/sala-congressi/ |
| EN | /en/conference-center/conference-hall/ |
Non era sufficiente anteporre /en/ allo stesso URL italiano.
Le traduzioni dei contenuti possono avere slug differenti e, con plugin come WPML o Polylang, ogni versione linguistica può essere gestita come un contenuto distinto.
Ho quindi previsto una gestione che tenesse conto di:
- Lingua associata al contenuto.
- Categoria Congressi e relativa traduzione.
- Base del permalink tradotta.
- Rewrite rules compatibili con la configurazione linguistica.
Per esempio, in un sito che utilizza directory linguistiche, la regola inglese può partire da questa struttura:
add_rewrite_rule(
'^en/conference-center/([^/]+)/?$',
'index.php?post_type=mphb_room_type&name=$matches[1]&lang=en',
'top'
);
La configurazione va però verificata con il plugin multilingua effettivamente installato: WPML e Polylang possono gestire diversamente i prefissi linguistici e le query di WordPress.
È inoltre necessario utilizzare le API del plugin multilingua per costruire i permalink nella lingua corretta. Hardcodare semplicemente /en/ può produrre risultati sbagliati quando il sito utilizza domini separati o cambia configurazione.
Il secondo problema SEO: Google interpretava le sale come camere
La modifica degli URL risolveva soltanto una parte del problema.
Dal punto di vista semantico, le sale congressi continuavano a essere registrate da MotoPress come tipologie di alloggio.
Per rendere più esplicita la differenza ho deciso di intervenire sui dati strutturati Schema.org.
Schema.org mette a disposizione due tipi pertinenti:
HotelRoom, per le camere d’albergo.MeetingRoom, per sale riunioni, sale conferenze e sale congressi.
La distinzione può essere gestita sfruttando la stessa funzione utilizzata per i permalink.
Un unico script per distinguere HotelRoom e MeetingRoom
add_action('wp_head', 'custom_mphb_schema', 25);
function custom_mphb_schema() {
if (!is_singular('mphb_room_type')) {
return;
}
$post_id = get_queried_object_id();
$url = get_permalink($post_id);
$type = custom_mphb_is_congress($post_id)
? 'MeetingRoom'
: 'HotelRoom';
$description = get_the_excerpt($post_id);
if (!$description) {
$description = wp_strip_all_tags(
strip_shortcodes(
get_post_field('post_content', $post_id)
)
);
}
$schema = [
'@context' => 'https://schema.org',
'@type' => $type,
'@id' => $url . '#room',
'name' => get_the_title($post_id),
'url' => $url,
'mainEntityOfPage' => $url
];
if (!empty($description)) {
$schema['description'] = $description;
}
$image = get_the_post_thumbnail_url(
$post_id,
'full'
);
if ($image) {
$schema['image'] = $image;
}
echo '<script type="application/ld+json">';
echo wp_json_encode(
$schema,
JSON_UNESCAPED_SLASHES |
JSON_UNESCAPED_UNICODE
);
echo '</script>';
}
Questo codice genera un JSON-LD differente in base alla categoria della risorsa.
Per le camere viene utilizzato HotelRoom; per le sale congressi viene utilizzato MeetingRoom.
Lo script è un esempio minimale: è possibile arricchire lo schema con capienza, servizi e altre proprietà, purché i valori siano effettivamente disponibili e coerenti con la pagina.
Prima di attivarlo bisogna inoltre verificare se il tema, MotoPress o il plugin SEO producono già dati strutturati relativi alla stessa risorsa, per evitare duplicazioni e descrizioni contraddittorie.
I dati strutturati garantiscono che Google riconosca la differenza?
No. Il markup Schema.org permette di descrivere con maggiore precisione il contenuto, ma Google non garantisce di utilizzarlo per modificare la visualizzazione o la classificazione delle pagine.
In particolare, se il problema riguarda le camere mostrate in Google Hotels attraverso un’integrazione di prenotazione o un feed esterno, il JSON-LD della pagina potrebbe non essere sufficiente.
I dati strutturati devono essere accompagnati da contenuti coerenti: titolo, descrizione, immagini e informazioni sulla destinazione d’uso della sala.
Come verificare che la personalizzazione funzioni
Prima di considerare concluso l’intervento, ho definito una serie di verifiche.
Permalink e redirect
- Le camere devono continuare a rispondere sui rispettivi URL
/accommodation/. - Le sale congressi devono essere raggiungibili sotto
/centro-congressi/. - I vecchi URL delle sale devono effettuare un redirect 301.
- Le versioni inglesi devono aprire il contenuto tradotto.
- Non devono verificarsi catene di redirect o errori 404.
Dati strutturati
Per controllare il JSON-LD si può utilizzare lo Schema Markup Validator.
È necessario verificare che le pagine delle camere contengano HotelRoom e quelle delle sale congressi MeetingRoom, senza eventuali markup incompatibili generati da altri componenti del sito.
Indicizzazione
Dopo la modifica degli URL occorre verificare che sitemap XML, canonical e collegamenti interni utilizzino i nuovi permalink. Conviene inoltre controllare le pagine con Google Search Console e richiederne una nuova scansione quando necessario.
Dove conservare il codice personalizzato
Durante lo sviluppo ho utilizzato snippet PHP, ma per una personalizzazione che interviene sul comportamento dei permalink e sui dati strutturati preferisco mantenere il codice in un plugin dedicato o in un MU-plugin.
Un MU-plugin viene caricato automaticamente da WordPress e non può essere disattivato dal normale elenco dei plugin.
Questo riduce il rischio di modifiche accidentali attraverso la dashboard amministrativa. Rimane comunque possibile intervenire sul file tramite accesso al server.
È anche una soluzione più indipendente dal tema rispetto all’utilizzo diretto del functions.php.
Cosa mi ha insegnato questo intervento
Il problema nasceva da un’esigenza legittima: utilizzare MotoPress Hotel Booking per gestire risorse prenotabili differenti all’interno della stessa struttura.
Dal punto di vista di WordPress, però, camere e sale congressi appartenevano allo stesso custom post type.
La soluzione ha richiesto di separare due aspetti che spesso vengono confusi: il permalink generato per un contenuto e la regola necessaria a renderlo raggiungibile.
A questo si sono aggiunte le esigenze del sito multilingua e la necessità di descrivere correttamente le risorse attraverso Schema.org.
La categoria Congressi è diventata il criterio comune per gestire tutte queste personalizzazioni.
È una soluzione utile anche in altri progetti WordPress nei quali un singolo custom post type contiene risorse che, per navigazione e SEO, hanno bisogno di percorsi e metadati diversi.
Scrivimi se hai bisogno di una soluzione ad un problema del tuo sito internet.
Riferimenti tecnici
- MotoPress – Modificare lo slug accommodation
- WordPress – Filtro post_type_link
- WordPress – add_rewrite_rule()
- Schema.org – HotelRoom
- Schema.org – MeetingRoom
- Google Search Central – Linee guida sui dati strutturati
Domande frequenti
Sì. WordPress mette a disposizione filtri come register_post_type_args e post_type_link, oltre alle rewrite rules. La scelta dipende dal fatto che si voglia modificare lo slug di tutte le risorse oppure soltanto di alcune.
Puoi controllare la tassonomia mphb_room_type_category con has_term() o wp_get_post_terms() e utilizzare post_type_link per modificare il permalink. Devi poi gestire le rewrite rules e i redirect.
Una causa possibile è che sia stato modificato il permalink senza registrare una rewrite rule corrispondente. Un’altra è che le regole non siano state rigenerate dopo la modifica. Nei siti multilingua possono intervenire anche le regole del plugin di traduzione.
Schema.org prevede il tipo MeetingRoom, adatto a sale conferenze e riunioni. Per una camera d’albergo è previsto HotelRoom.
Sì, ma occorre integrare la personalizzazione con il sistema multilingua utilizzato e verificare che ogni traduzione venga risolta correttamente da WordPress.