Joomla 4   MySQL 5.7 Python 3.9  Selenium 4.16

C'est quand même sympa les tags sur les articles Joomla. Ça permet de personnaliser certaines pages, créer des thématiques, des filtres... Ils font aussi office de sous-catégories, mais sans les inconvénients des sous-catégories (plusieurs tags possibles pour un même article). Et en plus, ça attire l'œil, notamment sous les titres ou dans un joli nuage de tags. Bref : moi j'aime bien 😀

Le seul truc relou, c'est que les utilisateurs ne pensent jamais à les mettre, arf !

Du coup je me suis dis : Ok, on va automatiser tout ça ! L'avantage est aussi d'être exhaustif quant à l'utilisation des tags, et ainsi de pouvoir les utiliser dans la logique même de son site.

Ci-dessous un exemple de trigger SQL assignant automatiquement des tags aux articles d'un site Joomla en fonction de leur titre.

Ajout des tags pendant l'enregistrement d'un article

Les grands principes sont les suivants :

  • On déclenche le TRIGGER dans la table #_content avec un AFTER UPDATE
  • Recherche d'une chaîne de caractères afin de choisir le tag pertinent
  • Précision de la (ou les) catégorie(s) d'articles qui seront ciblées
  • Suppression d'éventuels tags existants dans l'article
  • Récupération de l'id de contenu de l'article (table #_ucm_content)
  • Récupération de l'id du tag choisi (table #_tags)
  • Insertion des ids du contenu et du tag dans la table #_contentitem_tag_map
  • Séparation de toutes les insertions (avec une suite de conditions IF, sinon ça bloque une clé de la BDD Joomla pendant l'enregistrement de l'article)

Les tags en question doivent être créés en amont bien sûr, et il faudra enrichir la procédure selon les thématiques récurrentes de votre site.

Réfléchissez bien à la pertinence des chaînes de caractères à rechercher, c'est le plus important. N'hésitez pas à être explicite dans ces recherches, afin d'éviter les erreurs. Vous pouvez mettre un grand nombre de recherches pour un seul et même tag. Personnellement je scrute 50 chaînes, pour moins de 10 tags. C'est ultra-rapide et totalement invisible pour l'utilisateur (sauf quand il voit les tags apparaître 😁).

USE MaBdd ;
DROP TRIGGER IF EXISTS TriggerRemplissageTag ;
DELIMITER //
CREATE TRIGGER TriggerRemplissageTag AFTER UPDATE ON joo_content
FOR EACH ROW
BEGIN
 
-- SUPRIMER LES TAGS EXISTANTS SUR L'ARTICLE
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id ;
 
-- TAG INTERNATIONAL
IF NEW.catid = 8 AND NEW.title LIKE "%London%"
THEN
-- GET core_content_id
SET @CoreId = (SELECT core_content_id FROM joo_ucm_content WHERE core_content_item_id = NEW.id) ;
-- GET TAG ID
SET @TagId = (SELECT id FROM joo_tags WHERE title = "International") ;
-- DELETE TAG
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id AND tag_id = @TagId ;
-- FILL TAG IN CONTENT
INSERT INTO joo_contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id)
VALUES ('com_content.article', COALESCE(@CoreId,0), NEW.id, @TagId, NOW(), 1) ;
END IF ;
 
-- TAG SUCCESS STORY
IF NEW.catid = 8 AND NEW.title LIKE "%étudiant%"
THEN
-- GET TAG ID
SET @TagId = (SELECT id FROM joo_tags WHERE title = "Success story") ;
-- DELETE TAG
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id AND tag_id = @TagId ;
-- GET core_content_id
SET @CoreId = (SELECT core_content_id FROM joo_ucm_content WHERE core_content_item_id = NEW.id) ;
-- FILL TAG IN CONTENT
INSERT INTO joo_contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id)
VALUES ('com_content.article', COALESCE(@CoreId,0), NEW.id, @TagId, NOW(), 1) ;
END IF ;
 
-- TAG GÉOMATIQUE
IF NEW.catid = 8 AND (BINARY NEW.title LIKE "%OSM%" OR NEW.title LIKE "%Open Street Map%" OR NEW.title LIKE "%OpenStreetMap%")
THEN
-- GET TAG ID
SET @TagId = (SELECT id FROM joo_tags WHERE title = "Géomatique") ;
-- DELETE TAG
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id AND tag_id = @TagId ;
-- GET core_content_id
SET @CoreId = (SELECT core_content_id FROM joo_ucm_content WHERE core_content_item_id = NEW.id) ;
-- FILL TAG IN CONTENT
INSERT INTO joo_contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id)
VALUES ('com_content.article', COALESCE(@CoreId,0), NEW.id, @TagId, NOW(), 1) ;
END IF ;
 
-- TAG UNIVERSITÉ
IF NEW.catid = 8 AND NEW.title LIKE "%insertion pro%"
THEN
-- GET TAG ID
SET @TagId = (SELECT id FROM joo_tags WHERE title = "Université") ;
-- DELETE TAG
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id AND tag_id = @TagId ;
-- GET core_content_id
SET @CoreId = (SELECT core_content_id FROM joo_ucm_content WHERE core_content_item_id = NEW.id) ;
-- FILL TAG IN CONTENT
INSERT INTO joo_contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id)
VALUES ('com_content.article', COALESCE(@CoreId,0), NEW.id, @TagId, NOW(), 1) ;
END IF ;
 
-- TAG ÉVÉNEMENT
IF NEW.catid = 8 AND NEW.title LIKE "%hackathon%"
THEN
-- GET TAG ID
SET @TagId = (SELECT id FROM joo_tags WHERE title = "Événement") ;
-- DELETE TAG
DELETE FROM joo_contentitem_tag_map WHERE content_item_id = NEW.id AND tag_id = @TagId ;
-- GET core_content_id
SET @CoreId = (SELECT core_content_id FROM joo_ucm_content WHERE core_content_item_id = NEW.id) ;
-- FILL TAG IN CONTENT
INSERT INTO joo_contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id)
VALUES ('com_content.article', COALESCE(@CoreId,0), NEW.id, @TagId, NOW(), 1) ;
END IF ;
 
END ;//
DELIMITER ;

Omax ! Ce trigger va ajouter les tags souhaités par le webmaster sans effort pour l'utilisateur 😎

Ok mais... Quid des articles existants ? Et si je veux rajouter un tag ? Et si je m'aperçois qu'il manque la recherche de telle ou telle chaîne pour être efficace ?

C'est là que Python intervient, avec la bibliothèque Selenium.

Remplir les tags des articles existants

 La version 4.16 de Selenium est vraiment cool. Plus besoin d'aller chercher le bon driver, correspondant à la bonne version de son navigateur, cela toutes les 2 semaines...

Les grands principes sont les suivants :

  • Export d'un XML contenant les titres de tous les articles du site (le champ title de la table #_content) - on a là une piste d'amélioration : il serait plus efficace d'aller chercher la liste directement en SQL
  • Connexion à l'administration du site (ajoutez vos accès)
  • Accès à la page de gestion des articles
  • Modification du nombre d'articles affichés (All)
  • Ouverture d'un article
  • Enregistrement de l'article (sans aucune modification, c'est le trigger qui fera le taf) - et là je me dis que c'était peut-être inutile, un simple UPDATE directement en base suffirait-il à déclencher le TRIGGER ? 😅
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
import xml.etree.ElementTree as ET
 
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
 
browser = webdriver.Chrome()
browser.get('https://www.google.com/')
 
tree = ET.parse('C:/Users/Georges/Downloads/MonFichierXml.xml')
root = tree.getroot()
 
browser.maximize_window()
browser.get('https://mon-site-joomla.fr/administrator')
 
# CONNEXION
username = browser.find_element("id", "mod-login-username")
password = browser.find_element("id", "mod-login-password")
 
username.send_keys('MonSuperUtilisateur')
password.send_keys('MotDePasse')
 
browser.find_element("id", "btn-login-submit").click()
 
browser.get('https://mon-site-joomla.fr/administrator/index.php?option=com_content&view=articles')
time.sleep(1)
browser.find_element("id", "list_limit").click()
time.sleep(1)
browser.find_element("xpath", "//select/option[@value='0']").click()
 
for my_title in root.findall('MonFichierXml'):
    xml_title = my_title.find('title').text
    xml_title = xml_title.replace(' ', ' ')
 
    print(xml_title)
 
    element = browser.find_element(By.LINK_TEXT, xml_title)
    time.sleep(1)
    browser.execute_script("arguments[0].click();", element)
    time.sleep(1)
    browser.find_element("id", "save-group-children-save").click()
    time.sleep(1)
 
browser.quit()

Et voilà ! Y'a plus qu'à lancer le bouzin ! Cela autant de fois que nécessaire si vous modifiez vos tags ou votre trigger.

Et en plus, c'est rigolo 😂