Afficher les articles connexes, triés par pertinence

Quelques noisettes et une goutte de php pour afficher les articles qui partagent le plus de mot-clés avec un autre article.

Les articles connexes

Il est très intéressant de pouvoir présenter à nos visiteurs les articles qui traitent du même sujet que celui qu’ils sont en train de lire. Cela peut se faire simplement en listant les articles présents dans la même rubrique. On peut aussi le faire plus finement, en présentant aux visiteurs la liste des articles qui partagent les mêmes mots-clés que l’article qu’ils sont en train de consulter. Ce sont les articles connexes (pas forcément dans la même rubrique, mais reliés par leurs mots-clés à l’article courant).

Avec Spip, il est simple de faire la liste des articles connexes. Pour chaque mot-clé rattaché à l’article consulté, on affiche la liste des articles qui ont ce mot-clé. Cela donne la noisette suivante à placer dans vos squelettes d’articles :

<ul>
<BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>[(#REM) Pour chaque mot clé de l'article]
	<BOUCLE_Articles_Connexes(ARTICLES){!id_article}{id_mot}{doublons}>[(#REM) Pour chaque article qui a ce mot clé]
	<li>
		<a href="#URL_ARTICLE" [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a>
	</li>

	</BOUCLE_Articles_Connexes>
</BOUCLE_Mots_De_Article_Contexte>
</ul>

Cette noisette qui contient une boucle dans une autre qui affiche le titre et un lien de chaque article qui a un mot-clé commun avec l’article de contexte [1]. Ainsi, on a la liste des articles connexes. On peut affiner en informant le visiteur des mots-clés communs grâce à cette autre noisette à placer dans la boucle _Articles_Connexes :

 
		<B_Mots_Communs>
		Nombre de mots-clés communs&nbsp;: [(#_Mots_Communs:TOTAL_BOUCLE)]. 
(Liste des mots-clés communs&nbsp;:
		<BOUCLE_Mots_Communs(MOTS){id_article=#_Contexte_Article:ID_ARTICLE}{id_article}{id_mot!=35}{par type}{par titre}{"&nbsp;- "}>
		<a href="#URL_MOT" [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a>
		</BOUCLE_Mots_Communs>
			)

Dans cette noisette, #_Contexte_Article:ID_ARTICLE signifie #ID_ARTICLE de la boucle _Contexte_Article, à ne pas confondre avec l’#ID_ARTICLE de la boucle dans laquelle on place la noisette (_Articles_Connexes). Pour en savoir plus sur la syntaxe des balises du type : #_Nom_Boucle:#NOMBALISE allez consulter la documentation de Spip sur les Balises non ambiguës.

Même chose pour #_Mots_Communs:TOTAL_BOUCLE.

Donc, vous devez adapter cette petite noisette à votre cas, si vous êtes dans une boucle globale qui se nomme _Contexte_Article, pas de problème ; sinon, n’oubliez pas de changer son nom.

Tout ça, c’est très bien... Mais à condition d’avoir peu d’articles, et de mots-clés. Car, si comme moi, vous avez beaucoup d’articles et qu’ils ont chacun beaucoup de mots-clés ; la liste des articles connexes devient rapidement interminable (songez qu’elle contient tous les articles qui ont au moins un mot-clé commun avec l’article visité !). Pour éviter d’avoir 200 articles listés à chaque fois on peut se contenter de mettre dans la boucle _Articles_Connexes un critère {0,20} qui liste les 20 premiers articles par mots-clés. Bien sûr si vous avez beaucoup de mots-clés, il faudra lister moins de 20 articles par mots-clés, et peut-être pas tous les mots-clés non plus... Beaucoup de contraintes finalement et une solution peu satisfaisante qui est de prendre les x premiers articles des n premiers mots-clés, sans tenir compte de la pertinence de ce choix (dans les x premiers articles, il peut y en avoir qui n’ont qu’un seul mot-clé commun avec celui visité, alors qu’on voudrai plutôt afficher ceux qui en ont le plus).

Comment trier les —trop nombreux— articles connexes par pertinence et n’afficher que les plus intéressants pour le visiteur ?

Principe de la contrib’

Les articles connexes les plus pertinents sont ceux qui ont le plus de mots-clés communs avec l’article consulté. C’est ceux-là que l’on veut voir s’afficher en premier.

La contrib’ adapte donc la noisette présentée plus haut, qui est classique, pour pouvoir compter le nombre de mots-clés communs associés à chaque article connexe.

Spip, permet depuis la version 1.9.2 de faire des tableaux et de les manipuler avec les balises #SET et #GET introduites avec Spip 1.9.1 (voir la documentation de Spip). Mais, pour bien comprendre comment les utiliser, il faudra au préalable comprendre comment faire sans les utiliser. Pour cela il faut utiliser du PHP (les impatients peuvent tout de suite aller lire la noisette pour Spip 1.9.2)

Noisette et noix de coco

Il faut toujours être dans un contexte d’article (donc que id_article soit défini).

On met en place le compteur pour chaque article qui a des mots-clés communs. Avec ce compteur on compte le nombre de mots-clés en commun. C’est dans un tableau en PHP. Voilà la noisette :

<BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>[(#REM) Pour chaque mot clé de l'article]
	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>[(#Pour chaque article qui a ce mot clé]
<?php
# Un peu de PHP.
# on inclémente le compteur de pertinence:
# $pertinence est un tableau qui contient pour chaque id_article
# un compteur du nombre de mots-clés communs
$pertinence['[(#ID_ARTICLE|texte_script)]']++;
?>
	</BOUCLE_Tableau_Articles_Connexes>
</BOUCLE_Mots_De_Article_Contexte>

Le tableau $pertinence[ ] contient donc la liste des articles ayant des mots-clés communs avec notre article de contexte. Il contient aussi, pour chaque article de la liste, une valeur de pertinence qui correspond au nombre de mot-clés communs avec l’article de contexte.

On veut afficher les articles qui ont plus de 4 mots-clés communs (variable $maxcommun à changer selon vos goûts) avec notre article de contexte. On ne veut pas afficher plus de 20 articles (variable $maxarticles à changer selon vos goûts) au total.

Cette petite noisette grosse noix de coco est entrelacée dans du php : le tableau est trié de manière à ce que les articles les plus pertinents soient traités en premier. Puis, pour chaque élément du tableau, on va faire une boucle qui affiche un lien vers l’article, le nombre de mots-clés communs et la liste de ces mots-clés.

Et cela, seulement si la pertinence est suffisante (condition sur $maxcommun) et qu’il y a moins de 20 articles connexes affichés (condition sur $maxarticles)

<?php
# Un peu de PHP.
# on trie le tableau $pertinence[ ] afin que
# les id_articles qui ont le plus de mots-clés
# communs avec l'article du contexte soient en 1er

arsort ($pertinence);

#Puis pour chaque id_article:

$i=0;
$maxarticles=20;
$maxcommun=4;
while (list($ks,$vs) = each($pertinence)) {
#$ks contient id_article, $vs sa pertinence
	if ($vs > maxcommun-1) {
		$i++;
		if ($i==maxarticles) break;
?>

<ul>

<BOUCLE_Articles_Connexes(ARTICLES)>[(#REM) Liste absolument tous les articles]

<?php
# Un peu de PHP.
# Vérifie si l'article est celui du tableau
# Si oui, on effectue les actions de la boucle, sinon
# on passe à l'article suivant.
# ce petit morceau de PHP est en fait une condition de boucle
# comparable à {id_article=$ks} qui ne fonctionne pas dans Spip
		if ('[(#ID_ARTICLE|texte_script)]'==$ks) {
?>
	<li>
		<a href="#URL_ARTICLE" [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a> 
	<B_Mots_Communs>
		Nombre de mots-clés communs&nbsp;: [(#_Mots_Communs:TOTAL_BOUCLE)]. 
		(Liste des mots-clés communs&nbsp;:
	<BOUCLE_Mots_Communs(MOTS){id_article=#_Contexte_Article:ID_ARTICLE}{id_article}{id_mot!=35}{par type}{par titre}{"&nbsp;- "}>
		<a href="#URL_MOT" [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a>
	</BOUCLE_Mots_Communs>
			)
		</li>
<?php
# Un peu de PHP.
# fin de la condition {id_article=$ks}
# on passe donc à l'article suivant
		}
?>

</BOUCLE_Articles_Connexes>

<?php
# Un peu de PHP.
# fin de cet id_article ($ks)
# on passe donc au suivant
	}	
};
?>
</ul>

Cette noix de coco est complexe parce qu’elle est imbriquée dans du PHP...

À propos de l’intégration du PHP

J’ai découvert pas mal de choses sur l’intégration du PHP dans Spip. Ce qu’il faut principalement retenir ce sont les étapes du travail de Spip, telles qu’elles sont expliquées par ARNO* [2] ici : « Le PHP est interprété après les boucles ».

Donc, quand on intègre du PHP dans Spip, on doit systématiquement le séparer du reste par des balises <?php et ?> qui indiquent où commence et où finit le PHP.

Si vous souhaitez, comme dans cette contrib’, utiliser des valeurs calculées par Spip, ou issues de la base de donnée dans votre script PHP, récupérez-les en suivant cette méthode : Comment récupérer une « variable spip » en une variable PHP ?

Autre découverte : on peut couper le script PHP par ces balises partout, même au milieu d’un test de condition, si on oublie pas de le finir un peu plus loin (toujours encadré par les balises <?php et ?>). C’est ce que je fais dans la seconde noisettex, en commençant un test sur le tableau (« while (list($ks,$vs) = each($pertinence)) { » dont la fin se situe plus loin dans un autre « bloc » de PHP).

Dernière découverte et grosse difficulté (exposée par ARNO*) : on ne peut pas récupérer de variable PHP dans Spip car les BOUCLE/#BALISE sont transformées en PHP avant que le contenu du fichier ne soit lu par le serveur web ! Heureusement, les contributeurs ont trouvé deux solutions.
-  l’une simple que j’ai adopté, qui est expliquée ici : utiliser une variable PHP pour sélectionner une rubrique (par exemple). Ce n’est pas très élégant, car cela m’oblige à faire une boucle dans laquelle l’ensemble des articles est sélectionné, ce qui coûte un peu au serveur web/MySQL.

-  L’autre est élégante et puissante, elle utilise l’environnement de la page : en allemand : Tricks mit HTTP-Vars (moi je ne parle pas l’allemand ! Mais, la voici en plus ou moins français : variable php dans un critère de boucle ?). Elle est trop complexe pour moi.

Sans PHP et en beaucoup plus rapide !...

(mise à jour du 12/05/2008)

La noix décrite plus haut fonctionne bien. Mais, comme elle contient du PHP, il y a des désavantages. Les principaux sont :

  1. Le PHP dans le squelette, ce qui en plus de ne pas être élégant, pose des problèmes d’efficacité avec le cache de Spip.
  2. Cette noix nécessite de faire plusieurs passages par une boucle (BOUCLE_Articles_Connexes) qui liste tous les articles. Sur de grosses bases de données, cela peut prendre des proportions considérables (vraiment...) !

Jusqu’à Spip 1.9.2 c’était la seule possible. En utilisant les #ARRAY de Spip, il est possible de faire sans PHP et donc d’éviter ces deux problèmes.

Le principe sera exactement le même : On va faire une première boucle imbriquée pour fabriquer un tableau qui liste les articles ayant des mots-clés communs avec une pondération (nombre de mots-clés communs). Puis nous allons afficher les articles ayant la plus forte pondération.

On ne peut éviter totalement de faire appel au PHP pour arriver à nos fins. Mais grâce aux filtres de Spip, nous pouvons éviter d’en mettre à l’intérieur du squelette. Les filtres sont appliqués sur les balises Spip. Des filtres personnalisés peuvent être définis dans le fichier mes_fonctions.php à placer dans le dossier squelettes. Des filtres peuvent aussi être des fonctions PHP. Nous allons utiliser les deux.

Si vous avez un fichier mes_fonctions.php, vous devrez rajouter le code ci-dessous dedans. Sinon, vous devrez en créer un que vous placerez dans votre dossier squelettes :

<?php
//Incrémente la valeur correspondant à une clé dans un tableau.
//Est utilisé pour incrémenter la pondération des articles.
function ponderation ($tableau,$cle) {
	$tableau[$cle]++;
	return $tableau;
}
?>
<?php
//Trie un tableau en fonction de ses valeurs
//Est nécessaire, car [(#GET{un_tableau}|asort)] ne semble pas fonctionner
function trie_tableau ($tableau) {
	arsort ($tableau);
	return $tableau;
}
?>

Nous y définissons deux filtres sur les tableaux. L’un incrémente la valeur correspondant à une clé d’un tableau, l’autre trie un tableau.

Reprenons notre squelette... Commençons par pondérer les articles en fonction de leurs mots-clés communs avec l’article de contexte :

[(#REM) Pour chaque mot clé de l'article]
<BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>

[(#REM) Pour chaque article qui a ce mot clé]
	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>
[(#SET{tableau_pertinence,
	[(#GET{tableau_pertinence}
		|ponderation {#ID_ARTICLE}
	)]
})]
	</BOUCLE_Tableau_Articles_Connexes>
</BOUCLE_Mots_De_Article_Contexte>

À la place du PHP, nous utilisons l’un de nos filtres pour augmenter la pondération de nos articles dans un tableau Spip (notez que le tableau est en fait initialisé par le filtre). La balise #SET sert à affecter une valeur à une variable Spip (ici tableau_pertinence), #GET permet de récupérer le contenu de la variable.

L’objectif est de pouvoir utiliser un tableau Spip pour notre boucle BOUCLE_Articles_Connexes :

<BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{un_tableau_spip}}>
</BOUCLE_Articles_Connexes>

Pour rendre notre tableau utilisable dans une boucle, il va falloir lui faire subir des transformations :

-  classer les articles par pondération (ils sont dans le désordre pour linstant)
-  Transformer le tableau pour qu’il soit une liste d’id_article seulement. Car les id_article sont pour l’instant les clés du tableau, les valeurs sont les pondérations elles-mêmes. Alors que la boucle aura besoin d’un tableau où les id_article seront des valeurs [3].

Voici la manœuvre :

[(#REM) On fabrique un tableau trié avec les id_article comme valeurs]
[(#SET{tableau_articles,
	[(#GET{tableau_pertinence}
		|trie_tableau
		|array_keys)]}
)]

Le premier filtre vient de notre fichier mes_fonctions.php, qui trie le tableau. Ce tableau subit un second filtre, qui est une fonction PHP qui renvoie un tableau avec seulement les clés d’un autre tableau : array_keys.

Nous aurons aussi besoin de connaître la pertinence maximum des articles. C’est la première valeur de notre tableau tableau_pertinence une fois trié :

[(#SET{pertinence_max,
	[(#GET{tableau_pertinence}
		|trie_tableau|reset)]}
)]

C’est reset qui retourne cette valeur dans notre variable pertinence_max (reset est une fonction PHP, utilisée comme filtre à l’instar de array_keys ci-dessus).

Passons aux choses sérieuses :

<BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{tableau_articles}}{0,20}>
	#TITRE<br />
</BOUCLE_Articles_Connexes>

Cette boucle écrit les titres des 20 articles ({0,20}) ayant le plus de mots-clés communs avec l’article passé en contexte.

Mais pourquoi avoir récupéré pertinence_max ? Pour afficher la pertinence en pourcentage : (pondération de l’article*100)/pondération_max. Soit en Spip :

[(#GET{tableau_pertinence}
	|table_valeur{#ID_ARTICLE}
	|mult{100}
	|div{#GET{pertinence_max}}
)]

table_valeur donne la valeur stockée dans un tableau pour une clé donnée (ici #ID_ARTICLE). Pour le reste, ce sont des filtres mathématiques de Spip.

Voilà ! là noix qui découle de tout ceci et qui affiche les 20 articles connexes de l’id_article de contexte est :

[(#REM) Initialisation des tableaux]
#SET{tableau_pertinence,#ARRAY}
#SET{tableau_articles,#ARRAY}
#SET{pertinence_max,#ARRAY}
[(#REM) Pour chaque mot clé de l'article]
<BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>

[(#REM) Pour chaque article qui a ce mot clé]
	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>
[(#SET{tableau_pertinence,
	[(#GET{tableau_pertinence}|ponderation{#ID_ARTICLE})]
})]
	</BOUCLE_Tableau_Articles_Connexes>
</BOUCLE_Mots_De_Article_Contexte>
[(#REM) On fabrique un tableau trié avec les id_article comme valeurs]
[(#SET{tableau_articles,
	[(#GET{tableau_pertinence}
		|trie_tableau
		|array_keys)]}
)]
[(#REM) On récupère la pondération maximale pour nos pourcentages]
[(#SET{pertinence_max,
	[(#GET{tableau_pertinence}
		|trie_tableau|reset)]}
)]
[(#REM) Cette boucle affiche le titre des 20 articles connexes, c'est-à-dire les 20 articles qui ont le plus de mots-clés communs avec celui du contexte.
Elle donne la pondération en pourcents.]
<BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{tableau_articles}}{0,20}>
	#TITRE (pertinence: 
[(#GET{tableau_pertinence}
	|table_valeur{#ID_ARTICLE}
	|div{#GET{pertinence_max}}
	|mult{100}
)]		%)<br />
</BOUCLE_Articles_Connexes>

Notez l’apparition de :

[(#REM) Initialisation des tableaux]
#SET{tableau_pertinence,#ARRAY}
#SET{tableau_articles,#ARRAY}
#SET{pertinence_max,#ARRAY}

sur les conseils de Aurélie dans les forums qui permet d’initialiser les tableaux grâce à #ARRAY pour éviter des messages d’erreur (notamment quand il y a des articles sans mots-clés).

N’oubliez surtout pas de créer ou de compléter votre fichier mes_fonctions.php sinon, ça ne va pas fonctionner !

Voilà, j’espère que cela vous sera utile. Je trouve en tout cas très agréable de pouvoir proposer à mes visiteurs une liste restreinte d’articles connexes qui sont toujours pertinents, car ils partagent un nombre de mots-clés important.

Et vive la navigation transversale !

Notes

[1Le contexte est une notion fondamentale de Spip : c’est un endroit où id_article est défini (c’est-à-dire soit dans une boucle « articles », soit en recevant id_article par inclusion).

[2l’un des papas de Spip. Voir aussi L’histoire minuscule et anecdotique de SPIP.

[3Pour ceux qui ne savent pas, rappelons en simplifiant, que les tableaux de Spip, comme les tableaux PHP sont des suites d’éléments indexés par des clés. À chaque clé (unique) correspond une valeur (pas forcement unique, elle.).

Le logo de cet article est un extrait d’une image de notafish que vous pouvez retrouver là : Chaîne du pont-levis elle est sous license cc : by-sa.

Discussion

14 discussions

  • Merci infiniment pour cette contribution, un article très clair même pour un débutant. Cette noisette du début de l’article m’a fait gagné beaucoup de temps...

    Répondre à ce message

  • 23

    Bonjour, j’ai testé la nouvelle méthode, sans le php. Mais il me retourne :

    Fatal error : Cannot increment/decrement overloaded objects nor string offsets in /mnt/109/sdc/7/5/.../mes_fonctions.php on line 100

    sachant que la ligne 100 correpond à $tableau[$cle]++ de la fonction ponderation...

    Quelqu’un a-t-il une idée ?

    • En fait, il semble que les fonctions php ne reconnaissent pas tableau_pertinence et tableau_articles comme des tableaux...

      Où peut-on préciser $tableau_x=array() ; ? Car dans les fonctions, le tableau se vide à chaque fois que celle-ci est appelée, et en dehors, ce n’est pas pris en compte puisque les boucles sont calculées avant le PHP...

    • Je n’ai pas ce problème chez... Peut-être un souci de versions ou de config PHP.

      Avez vous essayé d’initialiser les tableaux dans le squelette avec un #SET{tableau_articles,#ARRAY} et un #SET{tableau_pertinence,#ARRAY} au début de la noix (ou du squelette...) ???

      Si non, pouvez vous essayer et me dire ce que ça donne ?

    • Je suis chez Free.

      L’initialisation de cette manière ne marche pas non plus... Toujours le même message. Si je mets $tableau[cle]=$tableau[cle]+1 ; à la place de $tableau[cle]++ ;, je n’ai pas de problème avec la fonction pondération, la page s’affiche, mais sans rien en lieu et place des articles connexes et un message en haut qui me dit :

      Warning : arsort() expects parameter 1 to be array, string given in /mnt/109/sdc/7/5/.../mes_fonctions.php on line 108

      Warning : array_keys() [function.array-keys] : The first argument should be an array in /mnt/109/sdc/7/5/.../ecrire/public/composer.php(72) : eval()’d code on line 566

      Warning : arsort() expects parameter 1 to be array, string given in /mnt/109/sdc/7/5/.../mes_fonctions.php on line 108

      Warning : reset() [function.reset] : Passed variable is not an array or object in /mnt/109/sdc/7/5/.../ecrire/public/composer.php(72) : eval()’d code on line 571

    • Mhhh... C’est compliqué... Je pensais vraiment que le #SET{tableau_articles,#ARRAY} suffirait !

      J’ai deux pistes qui pourraient résoudre le problème et je vais te demander de les essayer car je ne peux tester puisque je ne reproduis pas l’erreur :

      Dans les deux cas il faut modifier la fonction ponderation dans mes fonctions.
      -  Piste 1 : remplacer $tableau[$cle]++; par (array)$tableau[$cle]++;.
      -  Piste 2 : rajouter au début de la fonction : If (!$tableau) {$tableau[$cle]=array()}
      -  Piste 3 : rajouter au début de la fonction : If (!$tableau) {$tableau[$cle]=0}

      J’espère que la première piste suffira... Tiens moi vite au courant.

    • Merci beaucoup de prendre du temps pour me répondre.

      ...mais ça ne marche toujours pas, j’obtiens toujours la même erreur... Je ne comprends pas pourquoi... Tu parlais de ma version de php... Tu penses que ça peut être du à quoi ?

    • Aucune des trois méthodes ne fonctionne ??? Elles donnent toutes le même résultat ??

      Je te parlais de version de PHP car ça fonctionne bien chez moi et sur un serveur de production tel que c’est dans l’article...

      Peux-tu encore faire un essai ? Toujours dans la fonction ponderation, avant $tableau[$cle]++, peux tu rajouter :

      $tableau= is_string($tableau)?unserialize($tableau):$tableau;
      $tableau= is_array($tableau)?$tableau:array();

      (code tiré du filtre table_valeur de Spip)...

    • En effet, aucun des trois ne marche.

      Pour le dernier, en ajoutant les deux lignes dans ponderation et trie_tableau, aucune erreur ne s’affiche en haut de la page, mais je n’ai aucun article connexe qui ne s’affiche. Cela a le même effet que lorsque j’écrivais $tableau_pertinence=array() ;. Autrement dit, on dirait que ça remet les tableaux à 0.

      Ne pourrait-on pas pas définir tableau_pertinence et tableau_articles comme des tableaux dans un filtre qui est appelée bien avant dans le squelette. J’ai essayé, mais ça ne marche pas...

    • Désolé... Pour le moment je sèche un peu... Car en fait, [(#SET{tableau_pertinence,#ARRAY})] devrait suffire à initialiser les tableaux... Peut être rajouter les accolades ? [(#SET{tableau_pertinence,#ARRAY{0,0}})] par exemple... Mais j’y crois pas trop...

      Vraiment, je ne vois pas ce qui cloche...

    • Ca ne marche pas non plus. J’ai aussi essayé en passant en PHP 5, et ça ne marche pas non plus. J’étais en PHP 4 avant...

    • En essayant de mettre les #SETtableau_pertinence,#ARRAY et #SETtableau_articles,#ARRAY, dans les boucles, j’obtiens la même chose qu’en les définissant comme des tableaux en php, c’est-à-dire que je n’ai plus d’erreur, mais la liste des articles similaires ne s’affiche pas. J’arrive donc bien à définir des tableaux avec #SETtableau_pertinence,#ARRAY. Il semble donc que mes tableaux se transforment en autre chose que des tableaux au cours du processus...

    • Bon, moi je n’y comprends pas grand chose, mais j’ai placé echo $tableau ; dans la fonction pondération, juste avant return $tableau ; J’ai aussi remplacé $tableau[cle]++ ; par $tableau[cle]=$tableau[cle]+1 ; (pour ne pas avoir l’erreur) pour voir ce qu’il me donnait.

      Voici ce qui apparaît en haut de la page (j’ai 5 mots clés) :

      ArrayArray 1Array 1 1 Ar1ay 1 1 Ar1ay 2 1 Ar1ay 2 2 Ar1ay 3 2 Ar1ay 4 2 Ar2ay 4 2 Ar2ay 4 3 Ar2ay 4 3 1Ar3ay 4 3 1 Ar3ay 5 3 1 Ar3ay 5 4 1

      Est-ce que ça peut aider à comprendre ce qui se passe ?

    • Si je mets echo $tableau[cle] ; à la place, j’ai AAAAAAAAAAAAA en haut de la page

    • Bon... On va passer par les grands moyens, comme les tableaux ne semblent pas reconnus comme tels quel par le filtre (ce qui est bien étrange...) on va les sérialiser et les dé-sérialiser à chaque fois.

      Il a des modifs à faire dans mes_fonctions.php... Le nouveau doit contenir :

      <?php
      //Incrémente la valeur correspondant à  une clé dans un tableau.
      //Est utilisé pour incrémenter la pondération des articles.
      function ponderation ($tableau,$cle) {
      	$tableau= is_string($tableau)?unserialize($tableau):$tableau;
      	$tableau= is_array($tableau)?$tableau:array();
      	$tableau[$cle]++;
      	return $tableau;
      }
      ?>
      <?php
      //Trie un tableau en fonction de ses valeurs
      //Est nécessaire, car [(#GET{un_tableau}|asort)] ne semble pas fonctionner
      function trie_tableau ($tableau) {
      	$tableau= is_string($tableau)?unserialize($tableau):$tableau;
      	$tableau= is_array($tableau)?$tableau:array();
      	arsort ($tableau);
      	return $tableau;
      }
      ?>

      Dans la noix il faut rajouter le filtre serialize. Ainsi, on transforme ce qui concerne la pondération en :

      [(#SET{tableau_pertinence,
      	[(#GET{tableau_pertinence}
      		|serialize
      		|ponderation{#ID_ARTICLE}
      	)]
      })]

      et ce qui concerne le tri en :

      [(#SET{tableau_articles,
      	[(#GET{tableau_pertinence}
      		|serialize
      		|trie_tableau
      		|array_keys)]}
      )]

      et on oublie pas au début de la noix d’initialiser les tableaux :

      #SET{tableau_articles,#ARRAY{''}}
      #SET{tableau_pertinence,#ARRAY{''}}

      En espérant que ça va fonctionner, sinon, je risque d’être à court d’idées !

      NB : Si vous voulez suivre la valeur de $tableau dans le filtre, préférez un print_r($tableau); à un echo $tableau; qui donnera moins d’infos (print_r permet d’afficher le contenu du tableau)

    • Aucune erreur ne s’affiche mais je n’ai rien dans ma liste d’articles similaires... Ce n’est pas très étonnant : quand on avait mis les mêmes chosos dans les fonctions, on obtenait ça. Les tableaux semblent vidés à chaque fois, ce qui est confirmé quand je mets print_r à la fin, j’obtiens : Array ( [2] => 1 ) Array ( [35] => 1 ) Array ( [19] => 1 ) Array ( [2] => 1 ) Array ( [19] => 1 ) Array ( [35] => 1 ) Array ( [19] => 1 ) Array ( [19] => 1 ) Array ( [2] => 1 ) Array ( [35] => 1 ) Array ( [52] => 1 ) Array ( [2] => 1 ) Array ( [19] => 1 ) Array ( [35] => 1 )

      Quand je mets print_r sans les dernières modifications), j’obtiens : Array ( [2] => 1 ) Array 1Array 1 1 Ar1ay 1 1 Ar1ay 2 1 Ar1ay 2 2 Ar1ay 3 2 Ar1ay 4 2 Ar2ay 4 2 Ar2ay 4 3 Ar2ay 4 3 1Ar3ay 4 3 1 Ar3ay 5 3 1 Ar3ay 5 4 1.

      Juste une idée : est-ce que ça ne viendrait pas la balise #GET ? En effet, la première fois que la fonction ponderation s’exerce, la variable tableau_pertinence n’a pas de valeur donc tout se passe bien, mais la deuxième fois, ce n’est pas la variable tableau_pertinence qui va entrer en lieu et place de $tableau, mais sa valeur, obtenue par #GETtableau_pertinence... Non ? Etes-vous sûr que dans votre code qui semble marcher chez vous, il n’y a pas une différence de parenthèse avec la version que vous proposez ici ?

      J’espère que je suis compréhensible...

    • Bonjour,

      Merci à Beurt pour cette contrib’ qui répond parfaitement à un de mes besoins !

      Néanmoins... J’ai le même problème que Jiou en local (EasyPHP 2.0b1 : PHP 5.2.0 - Apache 2.2.3 - MySQL 5.0.27) et sur OVH.
      Quelqu’un a-t-il trouvé une solution ?

      NB : j’ai testé l’« ancienne méthode » présentée ici. Pour les articles sans mot-clef commun, j’ai le même message d’erreur que précisé dans les commentaires - ça doit pouvoir se gérer.
      Mais surtout, les inconvénients énoncés à la sortie de la « nouvelle méthode » m’incitent à chercher un autre moyen...
      Etant une bille en PHP, je m’en remets aux bonnes âmes. :)

      Merci d’avance à ceux qui prendront le temps de me répondre.
      Forza Spip

    • Hélas, je n’ai toujours pas trouvé de solution. Est-ce qu’il y a seulement des warning qui s’affichent ou bien est-ce qu’en plus les articles connexes ne sont pas affichés ?

    • Quelques précisions sur les difficultés que j’ai rencontrées.

      Étape 1 : mes_fonctions, mais fonctionne pas

      -  La boucle dans article.html
      -  Le code PHP dans mes_fonctions.php (créé pour l’occasion, dans /squelettes/)

      Erreur(s) dans le squelette
      Erreur : filtre « ponderation » non défini, _Tableau_Articles_Connexes


      (Ce message apparait 5 fois.)

      J’ai lu (je ne retrouve pas où) qu’on ne pouvait charger qu’un seul fichier mes_fonctions.php à la fois, or j’en ai un dans le sous-répertoire couteau_suisse du plugin du même nom. (j’en ai aussi un dans /SpipClear, mais je n’utilise pas ces squelettes actuellement - et enfin j’ai un article_pdf_mes_fonctions.php dans /article_PDF, pour être complet)

      Bref, j’ai mis les deux fonctions de cette noisette dans mes_options.php et les fonctions sont bien reconnues.
      Je ne suis pas certain d’être bien « propre » avec ça, mais le fait d’avoir les mêmes messages d’erreur que mes petits copains plus haut me laisse penser que c’est pas le sujet (?).

      Étape 2 : Spip m’insulte et s’arrête

      -  La boucle toujours dans article.html
      -  Le code PHP dans mes_options.php

      Fatal error: Cannot increment/decrement overloaded objects nor string offsets in D:\Spip\ecrire\mes_options.php on line 43

      La ligne 43 étant $tableau[$cle]++;

      Étape 3 : Spip m’insulte mais continue

      -  La boucle toujours dans article.html
      -  Le code PHP toujours dans mes_options.php
      -  Comme suggéré plus haut, je remplace (ligne 43) $tableau[$cle]++; par $tableau[$cle] = $tableau[$cle]+1;
      Bizarrement (mais toujours comme Jiou), le résultat est différent...

      Warning: arsort() expects parameter 1 to be array, string given in D:\Spip\ecrire\mes_options.php on line 51
      
      Warning: array_keys() [function.array-keys]: The first argument should be an array in D:\Spip\ecrire\public\composer.php(72) : eval()'d code on line 581
      
      Warning: arsort() expects parameter 1 to be array, string given in D:\Spip\ecrire\mes_options.php on line 51
      
      Warning: reset() [function.reset]: Passed variable is not an array or object in D:\Spip\ecrire\public\composer.php(72) : eval()'d code on line 587

      Contrairement à l’erreur fatale de tout à l’heure, ce ne sont que des Warning, donc l’article apparait... Mais pas les articles connexes (nada).

      Pour info, j’ai écrit quelques mots à différents endroits dans la noisette pour voir où Spip passait :

      [(#REM) Pour chaque mot clé de l'article]
      <BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>
      Passage n°1
      [(#REM) Pour chaque article qui a ce mot clé]
      	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>
      [(#SET{tableau_pertinence,
      	[(#GET{tableau_pertinence}
      		|ponderation{#ID_ARTICLE}
      	)]
      })]
      Passage n°2
      	</BOUCLE_Tableau_Articles_Connexes>
      Passage n°3
      </BOUCLE_Mots_De_Article_Contexte>
      [(#REM) On fabrique un tableau trié avec les id_article comme valeurs]
      Passage n°4
      [(#SET{tableau_articles,
      	[(#GET{tableau_pertinence}
      		|trie_tableau
      		|array_keys)]}
      )]
      Passage n°5
      [(#REM) On récupère la pondération maximale pour nos pourcentages]
      [(#SET{pertinence_max,
      	[(#GET{tableau_pertinence}
      		|trie_tableau|reset)]}
      )]
      Passage n°6
      [(#REM) Cette boucle affiche le titre des 20 articles connexes, c'est-à-dire les 20 articles qui ont le plus de mots-clés communs avec celui du contexte.
      Elle donne la pondération en pourcents.]
      <BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{tableau_articles}}{0,20}>
      Passage n°7
      	#TITRE (pertinence: 
      [(#GET{tableau_pertinence}
      	|table_valeur{#ID_ARTICLE}
      	|div{#GET{pertinence_max}}
      	|mult{100}
      )]		%)<br />
      Passage n°8
      </BOUCLE_Articles_Connexes>

      Résultat :
      Passage n°1 Passage n°2 Passage n°2 Passage n°2 Passage n°2 Passage n°2 Passage n°2 Passage n°3 Passage n°4 Passage n°5 Passage n°6

      L’article testé n’a qu’un mot-clef, contenu par ailleurs dans 6 autres articles (d’où l’apparition à 6 reprises du passage n°2).
      Si ça peut vous aider à localiser le problème (parce que moi, pas des masses :D).

      Je suis aussi allé voir en var_mode=debug, et je crains que cela ne dépasse mon seuil de compétences...

      A disposition pour vous donner plus d’éléments.

      Merci de chercher, Beurt. :)

    • Eurêka, j’ai trouvé ! En tout cas, chez moi, ça marche.

      En fait, j’ai modifié :

      [(#SET{tableau_pertinence,
      	[(#GET{tableau_pertinence}
      		|ponderation{#ID_ARTICLE}
      	)]
      })]

      en enlevant certaines [(, ce qui donne :

      [(#SET{tableau_pertinence,
      	#GET{tableau_pertinence}
      		|ponderation{#ID_ARTICLE}
      	
      })]
    • Bravo Jiou, cette modif me permet à moi aussi de faire fonctionner le script :)

      Et encore merci Beurt pour cette contrib’ très efficace.

    • Christophe Noisette

      Merci itou. La modif proposée règle aussi chez moi le problème.

    • Merci Jiou j’avais également le même problème.
      Ca vaudrait le coup d’inclure cette proposition de modif dans l’article non ?

    • Je ne rajoute pas l’astuce Jiou car elle ne semble pas nécessaire partout. Notamment, sur l’ensemble des sites dont je m’occupe ce n’est pas nécessaire. De plus l’écriture des balises sous la forme [(#BALISE{parametres}|filtre{parametre_filtre})] est l’écriture normale, disons garantie. L’écriture simplifiée sans accolade/parenthèse n’est que dérogatoire et moins garantie. En conséquence ça devrait mieux fonctionner avec les accolades/parenthèses que sans.

      Reste à comprendre pourquoi ce n’est pas le cas ici !

    • Je me permets de confirmer l’astuce de Jiou mais en précisant que cela n’est nécessaire que pour les #GET étant utilisé en deuxieme parametre de #SET, exemples :

      [(#SETtableau_pertinence,
      #GETtableau_pertinence,#ARRAY|ponderation#ID_RUBRIQUE
      )]

      [(#SETtableau_rubriques,
      #GETtableau_pertinence,#ARRAY|trie_tableau|array_keys
      )]

      [(#SETpertinence_max,
      #GETtableau_pertinence|trie_tableau|reset
      )]

      Cette écriture est donc nécessaire pour faire marcher cette astuce / noisette / noix de coco. (testé sous SPIP 3.1)

      Merci pour cette contrib
      ++

    Répondre à ce message

  • 3
    Thiébaut

    Bonjour, je rencontre des difficultés à utiliser votre contribution, qui correspond exactement à ce que je souhaite.

    J’ai copié la dernière versions de la boucle ( 35 lignes) vers mon squelette d’article
    et créé un dossier mes_fonctions.php avec les 16 lignes de code php...

    Or rien ne se passe... hormis le fait que le mes_fonctions.php semble désactiver le cookie de correspondance puisque je n’ai plus les onglets « recalculer » et « espace privé ».

    Par ailleurs, quand je parviens à recalculer la page, même un autre squelette que l’article, j’ai un écran blanc avec le message suivant :

    Fatal error : Cannot increment/decrement overloaded objects nor string offsets in /var/alternc/html/o/openfreeclimbing/valleeducousin/squelettes/mes_fonctions.php on line 5

    ou cet autre message :

    HTTP 302
    Si votre navigateur n’est pas redirigé, cliquez ici pour continuer.

    quand je vais sur la page « vider le cache » j’obtiens ceci :

    V

    ider le cache

    Utilisez cette commande afin de supprimer tous les fichiers présents dans le cache SPIP. Cela permet par exemple de forcer un recalcul de toutes les pages si vous avez fait des modifications importantes de graphisme ou de structure du site.

    Si vous voulez recalculer une seule page, passez plutôt par l’espace public et utilisez-y le bouton « recalculer ».
    HTTP 302
    Si votre navigateur n’est pas redirigé, cliquez ici pour continuer.
    Images calculées automatiquement

    Warning : Cannot modify header information - headers already sent by (output started at /var/alternc/html/o/openfreeclimbing/valleeducousin/squelettes/mes_fonctions.php:1) in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/inc/actions.php on line 89

    Les images calculées automatiquement par SPIP (vignettes des documents, titres présentés sous forme graphique, fonctions mathématiques au format TeX...) occupent dans le répertoire local/ un total de 14.9 Mo.

    Un autre message également :

    Warning : include_once(/var/alternc/html/o/openfreeclimbing/valleeducousin/squelettes/mes_fonctions.php) [function.include-once] : failed to open stream : No such file or directory in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/inc/autoriser.php on line 45

    Warning : include_once() [function.include] : Failed opening ’/var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/../squelettes/mes_fonctions.php’ for inclusion (include_path=’. :/usr/share/php :/usr/share/pear’) in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/inc/autoriser.php on line 45

    Warning : include_once() [function.include-once] : Unable to access /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/../squelettes/mes_fonctions.php in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/public/parametrer.php on line 21

    Warning : include_once(/var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/../squelettes/mes_fonctions.php) [function.include-once] : failed to open stream : No such file or directory in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/public/parametrer.php on line 21

    Warning : include_once() [function.include] : Failed opening ’/var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/../squelettes/mes_fonctions.php’ for inclusion (include_path=’. :/usr/share/php :/usr/share/pear’) in /var/alternc/html/o/openfreeclimbing/valleeducousin/ecrire/public/parametrer.php on line 21

    Bref... pas mal d’erreurs dues à mon avis à mes_fonctions.php, car dès que je l’enlève, tout refonctionne.

    Pour info, ma version de SPIP est 3.0.10

    Avez-vous une idée sur l’origine du problème ? Merci !

    • Une solution plus simple, Articles connexes, par pertinences, avec mots (à consulter, pour le moment, dans l’espace privé)

    • Bonjour,

      c’est un problème nouveau que tu rencontres. Dans mes_fonctions.php essaie de rajouter entre la ligne 4 et 5 une ligne contenant : $tableau = array($tableau);. Est-ce que ça fonctionne mieux ?

    • Thiébaut

      Merci d’avoir suivi l’affaire... seulement la solution proposée par Maïeul correspond à ce que j’attendais. Je n’ai pas donc pas le courage de tester à nouveau : j’espère que vous voudrez bien m’en pardonner.

    Répondre à ce message

  • 7

    Bonjour, et merci pour cette contrib qui me sert assez régulièrement !

    Je voudrais savoir comment afficher les résultats dans une boucle conditionnelle, car, pour le moment, si l’article n’a aucun mot-clé (ce qui arrive souvent dans le projet sur lequel je travaille en ce moment), le squelette me renvoie une liste de messages d’erreurs et de warning...

    Si quelqu’un a une réponse il me serait d’un grand secours...

    Merci
    Abel

    • Bonjour,

      Est-ce la boucle <BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}> qui te pose souci ?

      [(#REM) Pour chaque mot clé de l'article]
      <BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>
      
      [(#REM) Pour chaque article qui a ce mot clé]
          <BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>
      [(#SET{tableau_pertinence,
          [(#GET{tableau_pertinence}
              |ponderation {#ID_ARTICLE}
          )]
      })]
          </BOUCLE_Tableau_Articles_Connexes>
      </BOUCLE_Mots_De_Article_Contexte>

      Si c’est le cas, il doit être possible de faire un peu différemment en utilisant l’excellent plugin Plugin « Critère mots »... À tester...

    • Merci pour la réponse.
      Oui, le problème doit provenir de cette boucle. je ne vois pas comment éviter les messages d’erreurs.

      Voici les warnings que je reçois :

      Warning: arsort() expects parameter 1 to be array, string given in /monsite/squelettes/mes_fonctions.php on line 13
      
      Warning: array_keys() [function.array-keys]: The first argument should be an array in /monsite/ecrire/public/composer.php(69) : eval()'d code on line 23
      
      Warning: arsort() expects parameter 1 to be array, null given in /monsite/squelettes/mes_fonctions.php on line 13
    • Bon finalement ce n’est pas très grave : les warnings ne s’affichent que pour les auteurs identifiés sur le site. Ils ne défigurent donc pas le site pour les visiteurs.

      Encore merci pour cet excellente contrib !

    • À mon avis, il faut initialiser tous les tableaux avant les boucles :

      #SET{tableau_pertinence,#ARRAY}
      #SET{tableau_articles,#ARRAY}
      #SET{pertinence_max,#ARRAY}
    • Super !

      Merci beaucoup Aurélie cela semble fonctionner au poil !!!

    • Merci Aurélie, je le rajoute dans l’article.

    • Désolé mais je dois avouer être un peu perdu apres avoir suivi tout ce fil !

      Si je copie le squelette et les fonctions php de l’article, il ne se passe tout simplement rien !

      Ai-je mal fait ? Choisi le mauvais code ?
      Merci d’avance

    Répondre à ce message

  • 1

    You can also use this code inside an articles loop :

    _ 	#SET{mots,#ARRAY}
    	<BOUCLE_mots(MOTS){id_article}>
    	#SET{mots, #GET{mots}|push{#ID_MOT}}
    	</BOUCLE_mots>
    	<ul>
    	<BOUCLE_aa(ARTICLES spip_mots_articles spip_mots) {id_mot IN #GET{mots}}>
    	<li><a href="#URL_ARTICLE">#TITRE</a></li>
    	</BOUCLE_aa>    
    	</ul>
    • Yes, but as the loop given at the top of this article, you cannot have the n first articles with the maximum of common words. The principle of this contrib’ and mainly the last part is to have closest articles with the maximum of common words.

    Répondre à ce message

  • 1
    patrice47310

    Bonjour,

    Merci pour cette super contrib, j’ai juste une question.
    Comment faire pour limiter le nombre de chiffres aprés la virgule pour la pertinence ?

    Merci

    • Je pense —sans avoir testé— que tu peux utiliser la fonction php intval().

      Par exemple, cela pourrait donner

      [(#GET{tableau_pertinence}
              |table_valeur{#ID_ARTICLE}
              |div{#GET{pertinence_max}}
              |mult{100}
              |intval
      )%]

      Attention ce n’est pas un arrondi : intval(4.2) donne 4 et intval(4.7) donne 4 aussi !

    Répondre à ce message

  • 1
    Christophe Noisette

    Rerere... décidément je monopolise un peu le forum. Ne m’en veuillez pas, mais j’expérimente la contrib’ et donc je trouve des choses curieuses que je partage ici.

    J’ai un article (id_article=3526) qui a les mots clés suivant : [Allemagne | Etiquetage | sans OGM | Juridique et réglementaire].

    Curieusement, la contrib’ va considérer que l’article (id_article=50) qui a les mots clés [Asie | Etiquetage (COMMUN) | Juridique et réglementaire (COMMUN)].
    Donc 2/3 = 66%

    EST PLUS PERTINENT que l’article (id_article=4203) qui a les mots clés [France | Enjeu économique | Etiquetage (COMMUN) | Loi française sur les OGM | sans OGM (COMMUN) | Juridique et réglementaire (COMMUN)].
    Donc 3/6 = 50%

    C’est un peu bizarre car pour moi, le deuxième article est plus pertinent car il a plus de mots clés en commun avec l’article de base.
    Est-ce possible de modifier cela pour que l’article 4203 apparaisse avant l’article 50 ?
    Merci infiniment
    C.

    • Christophe Noisette

      Désolé ce commentaire est à supprimer car l’erreur ne vient pas de la contrib’ mais de contrainte que j’avais placé dans ma boucle, à savoir un id_secteur=2... Mea culpa et désolé du dérangement.

    Répondre à ce message

  • 18
    Christophe Noisette

    Re.... donc la contrib est super, elle marche bien... Mais je comprends pas pourquoi sur mes pages (cf. http://www.infogm.org/spip.php?article4348), la pertinence reste à 0% alors que, pour le premier article proposé dans la liste des articles connexes j’ai 4 mots clés communs sur 4... donc la pertinence aurait dû être à 100%, non ? J’ai modifié le code avec l’information donnée par Jiou, le 22 juillet 2008.
    Encore merci pour la contrib’.

    • Salut !

      Peux-tu redonner ici un extrait du code du squelette qui affiche la pertinence chez toi ?

    • Christophe Noisette

      bonjour
      j’ai recopié le code proposé, sans l’avoir réellement modifié. J’ai fait quelques modif de forme et non pas de strucutre.
      Ainsi, voici le code que j’ai mis sur la page article.html

      [(#REM) Pour chaque mot clé de l'article]
      <BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}{id_groupe !==^(15|14)}>
      
      [(#REM) Pour chaque article qui a ce mot clé]
      	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}{id_secteur=2}{!par date}>
      
      [(#SET{tableau_pertinence,
              #GET{tableau_pertinence}
                      |ponderation{#ID_ARTICLE}
             
      })]
      
      	</BOUCLE_Tableau_Articles_Connexes>
      </BOUCLE_Mots_De_Article_Contexte>
      [(#REM) On fabrique un tableau trié avec les id_article comme valeurs]
      [(#SET{tableau_articles,
      	[(#GET{tableau_pertinence}
      		|trie_tableau
      		|array_keys)]}
      )]
      [(#REM) On récupère la pondération maximale pour nos pourcentages]
      [(#SET{pertinence_max,
      	[(#GET{tableau_pertinence}
      		|trie_tableau|reset)]}
      )]
      [(#REM) Cette boucle affiche le titre des 10 articles connexes, c'est-a-dire les 10 articles qui ont le plus de mots-cles communs avec celui du contexte.
      Elle donne la pondération en pourcents.]
      <BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{tableau_articles}}{0,10}{doublons pourress}>
      	<A href="#URL_ARTICLE" class="titreArt4">#TITRE</A></Font>
       <font class="texte_courant_petit">(pertinence: 
      [(#GET{tableau_pertinence}
      	|table_valeur{#ID_ARTICLE}
      	|div{#GET{pertinence_max}}
      	|mult{100}
      )]%)([(#DATE|nom_mois)] [(#DATE|annee)])<br /></font>
      </BOUCLE_Articles_Connexes>

      Le fichier « mes_options » contient exclusivement le code que tu as proposé dans cette contrib’.

      Merci
      Christophe

    • Peux-tu essayer de remplacer

      [(#GET{tableau_pertinence}
              |table_valeur{#ID_ARTICLE}
              |div{#GET{pertinence_max}}
              |mult{100}
      )]

      Par

      [(#GET{tableau_pertinence}|table_valeur{#ID_ARTICLE}|div{#GET{pertinence_max}}|mult{100})]

      (idem, mais sans les espaces et les indentations)

      Et me dire si ça va mieux ?

    • Christophe Noisette

      Merci... mais je ne vois pas de différence entre les deux codes. Malgré cela, j’ai copié ta proposition de code sur ma page et rien n’a changé. Merci d’avoir pris le temps de me répondre.
      _C.

    • Il n’y a pas de différence sauf les espaces et les indentations : Spip est des fois chatouilleux avec les espaces entre les filtres, notamment lors de l’utilisation de tableaux.

      Mais visiblement ce n’était pas le problème...

      Pour essayer de comprendre, je pense qu’il serait intéressant de connaître le contenu du tableau tableau_pertinence aux différentes étapes de la noisette.

      Si tu utilises Spip>2.1 tu peux le faire avec [(#GET{tableau_pertinence}|foreach)]. Sinon c’est possible avec [(#GET{tableau_pertinence}|print_r{1})].

      Peux-tu me dire si le contenu du tableau retourné est conforme à ce qu’on attend : id_article → pertinence (ex. : pour le premier article tu dois avoir un truc du genre XXXX,4XXXX est l’id_article, et 4 car il y a 4 mots-clés communs)

      (et puis dans la foulée tu peux vérifier que pertinence_max n’es pas nul !)

    • Christophe Noisette

      J’ai fait une capture écran après avoir inséré [(#GETtableau_pertinence|print_r1)] dans mon code. Je ne comprends pas le résultat qui est retourné car le premier article trouvé par la contrib est normalement l’article id=3307 qui a trois mots communs avec celui qu’on visualise (id=4425).
      Pour t’aider, j’ai mis le bout de code après :

       <font class="texte_courant_petit">
      (pertinence: 
      
      [(#GET{tableau_pertinence}|table_valeur{#ID_ARTICLE}|div{#GET{pertinence_max}}|mult{100})]%)
      ([(#DATE|nom_mois)] [(#DATE|annee)])<br />
      </font>

      Enfin, je ne sais pas vérifier pertinence_max..

    • Ce qui est affiché est le contenu du tableau tableau_pertinence, sous la forme [clé] => valeur.

      Comme tu donnes un exemple, l’article 3307, ce serait intéressant de vérifier qu’on a bien quelque part [3307] => 3.

      Si c’est le cas, c’est que le tableau est correct.

      Dans ce cas, il faudra donc regarder ailleurs, peut-être du coté de pertinence_max.

    • Pour vérifier pertinence_max, il suffit de placer [pertinence_max:(#GET{pertinence_max})] dans la boucle concernée.

    • Christophe Noisette

      Donc j’ai bien dans la liste [3307] => 3... donc le tableau fonctionne.
      Quant à la valeur de pertinence_max, je n’arrive pas à la calculer.
      j’ai mis le bout de code
      [pertinence_max:(#GET{pertinence_max})]  
      a plusieurs endroits dans mon squelette article et je n’ai jamais rien obtenu.
      Je suis vraiment pas très doué ;)
      C.

    • Ok, si tu as mis [pertinence_max:(#GET{pertinence_max})] et que rien ne s’affiche, c’est que pertinence_max est nul et ça ne devrait pas (sinon ça fait une division par zero !!!)

      essaie de remplacer :

      [(#SET{pertinence_max,
      	[(#GET{tableau_pertinence}
      		|trie_tableau|reset)]}
      )]

      par [(#SET{pertinence_max,[(#GET{tableau_pertinence}|trie_tableau|reset)]})]

      Donc sans indentations ni espaces.

      Et là ça donne quoi ?

    • Christophe Noisette

      Ca change rien... toujours à 0%. J’en ai profité pour que l’ensemble du code soit sans indentations ni espaces... au regard de ton premier commentaire... en vain...

    • et si tu remplaces « trie_tableau » par « arsort » ?

      et si ça ne donne rien, peux tu rajouter dans ton code, juste après [(#SET{pertinence_max,[(#GET{tableau_pertinence}|trie_tableau|reset)]})]

      ---*-[(#GET{tableau_pertinence}|trie_tableau|reset)]--*- et ---*-[(#GET{tableau_pertinence}|arsort|reset)]--*-

      et me dire ce que ça donne ?

    • Christophe Noisette

      — Première solution

      [(#SET{pertinence_max,[(#GET{tableau_pertinence}|arsort|reset)]})] 

      donne le message d’erreur suivant :
      Fatal error : Only variables can be passed by reference in /homez.95/infogm/www/ecrire/public/composer.php(73) : eval()’d code on line 1604

      — Deuxième solution :

      [(#SET{pertinence_max,[(#GET{tableau_pertinence}|trie_tableau|reset)]})] 
      [(#GET{tableau_pertinence}|trie_tableau|reset)]
      [(#GET{tableau_pertinence}|arsort|reset)]

      donne le message d’erreur suivant :
      Fatal error : Only variables can be passed by reference in /homez.95/infogm/www/ecrire/public/composer.php(73) : eval()’d code on line 1611

    • et juste

      [(#SET{pertinence_max,[(#GET{tableau_pertinence}|trie_tableau|reset)]})]
      -*---[(#GET{tableau_pertinence}|trie_tableau|reset)]-*----

      ça fait quoi ?

      (laisse les -*----)

    • Christophe Noisette

      Ca fait que dans la colonne où s’affichent les articles connexes, j’ai au début de la liste :
      -*----*----
      Mais plus de message d’erreur.

    • attention ! on ne peut utiliser les function de type arsort (qui modifie l’objet passé) comme filtre SPIP.

      Un filtre SPIP passe un élement et obtient un autre en retour.

      Il faut définir dans mes_fonctions.php

      <?
      function pseudo_arsort($array){
      	arsort($array);
      	return $array;
      }
      ?>

      et appliquer le filtre pseudo_arsort.

    • Justement Maïeul, c’est le rôle du filtre trie_tableau de la contrib ! (regarde ! le code est exactement le même :-))

      Par contre, j’ai du mal à comprendre pourquoi chez Christophe, le filtre reset ne renvoie pas le premier élément du tableau comme ça le fait chez moi ou d’autres !

    • Christophe Noisette

      Donc, j’ai défini cette fonction dans mes_fonctins.php et j’ai écrit le code suivant

      [(#REM) On récupère la pondération maximale pour nos pourcentages]
      [(#SET{pertinence_max,[(#GET{tableau_pertinence}|pseudo_arsort|reset)]})]

      Et j’ai toujours une pertinence à 0%...
      Mais je n’ai aucun message d’erreur ;)

    Répondre à ce message

  • 3

    Bonjour,

    Je débute en PHP/Spip. Sauriez-vous comment trier d’abord par pertinence (comme c’est fait actuellement) puis, pour les articles ayant la même pertinence, par un autre critère (a priori date inverse).

    Actuellement, le tableau est trié grâce à arsort ($tableau);, et j’avoue ne pas savoir comment ajouter un deuxième critère...

    Des idées ?

    Merci d’avance !

    • Faire ce tri (par pertinence puis par date) n’est pas très simple.

      Pour expliquer je vais partir du principe que tu utilises la version « sans PHP et beaucoup plus rapide » relatée à la fin de la contrib’.

      Si dans la boucle qui affiche les articles on met un critère {!par date} on aurait :

      <BOUCLE_Articles_Connexes(ARTICLES){id_article IN #GET{tableau_articles}}{0,20}{!par date}>
      blabla
      </BOUCLE_Articles_Connexes>

      Et les articles seraient bien triés par date, mais plus du tout par pertinence (à moins que deux articles aient la même date à la seconde près, ces deux-là seraient alors affichés par ordre de pertinence : inutile).

      Comme on ne peut pas rajouter ce critère au moment de l’affichage, il faut sans doute le faire en amont : avant que le tableau ne soit trié par pertinence.

      Donc, quand on construit le tableau, on rajoute le (ou les) critère que l’on choisit (ici {!par date}) :

      <BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}{!par date}>
      [(#SET{tableau_pertinence,
      	[(#GET{tableau_pertinence}
      		|ponderation{#ID_ARTICLE}
      	)]
      })]
      	</BOUCLE_Tableau_Articles_Connexes>

      Dans le tableau tableau_pertinence les articles sont donc triés par date. Lorsque l’on transforme ce tableau en tableau_articles (par le filtre arsort dont tu parles), les articles sont retriés par pertinence, mais je pense que si leur pertinence est identique, ils gardent alors le tri initial (ici par date).

      Je ne sais pas si je suis clair ? :-/

      En résumé, je pense qu’en mettant le critère {!par date} à la boucle _Tableau_Articles_Connexes de cette contrib’ tu auras des articles triés par pertinence puis par date... Mais comme je n’ai pas testé, si tu essaies, tu me diras ! :-)

    • Je comprends bien ce que tu proposes, mais cela ne semble pas marcher... et je ne sais pas vraiment te dire où ça coince. J’ai tenté différents critères, et leurs inverses, je ne comprends pas DU TOUT comment ils sont interprétés...

      -  un exemple avec une brève d’un mot-clef ({par date}) :

      100 % (9 janvier 2009)
      100 % (10 janvier 2009)
      100 % (6 février 2009)
      100 % (24 novembre 2008)
      100 % (13 novembre 2008)
      100 % (27 octobre 2008)
      100 % (28 octobre 2008)
      100 % (8 novembre 2008)
      100 % (20 octobre 2008)

      -  le même exemple, en théorie juste inversé par rapport à ci-dessus... ({!par date}) :

      100 % (28 octobre 2008)
      100 % (27 octobre 2008)
      100 % (20 octobre 2008)
      100 % (8 novembre 2008)
      100 % (13 novembre 2008)
      100 % (10 janvier 2009)
      100 % (9 janvier 2009)
      100 % (24 novembre 2008)
      100 % (6 février 2009)

      -  un autre exemple ({par date}) :

      100 % (7 janvier 2009)
      100 % (15 janvier 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (16 janvier 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (31 janvier 2009)
      66.6666666667 % (13 novembre 2008)
      66.6666666667 % (24 octobre 2008)
      66.6666666667 % (9 décembre 2008)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (3 février 2009)
      66.6666666667 % (6 janvier 2009)
      66.6666666667 % (13 novembre 2008)
      66.6666666667 % (5 décembre 2008)
      66.6666666667 % (6 février 2009)
      66.6666666667 % (5 février 2009)
      66.6666666667 % (30 décembre 2008)
      66.6666666667 % (2 janvier 2009)
      (je coupe les 33%)

      -  le même exemple, en théorie juste inversé par rapport à ci-dessus... ({!par date}) :

      100 % (7 janvier 2009)
      100 % (15 janvier 2009)
      66.6666666667 % (5 décembre 2008)
      66.6666666667 % (30 décembre 2008)
      66.6666666667 % (2 janvier 2009)
      66.6666666667 % (6 janvier 2009)
      66.6666666667 % (6 février 2009)
      66.6666666667 % (5 février 2009)
      66.6666666667 % (24 octobre 2008)
      66.6666666667 % (13 novembre 2008)
      66.6666666667 % (13 novembre 2008)
      66.6666666667 % (3 février 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (9 décembre 2008)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (29 janvier 2009)
      66.6666666667 % (31 janvier 2009)
      66.6666666667 % (16 janvier 2009)
      (je coupe les 33%)

      Les critères influent donc sur le résultat, mais y a manifestement quelque chose qui cloche ;).

      Si tu as des idées de pistes à explorer... parce que là je ne sais pas t’en dire plus. Merci de ton aide.

    • Ah zut !

      Pour l’instant je n’ai pas d’idée... Il faudrait peut-être regarder du coté d’arsort pour voir s’il fait un tri secondaire sur les clés ? À ce moment là les articles seraient toujours triés par leurs id_article...

      Sinon, il y aurait un moyen plus lourd pour permettre ce tri qui serait de créer une troisième colonne au tableau tableau_pertinence contenant les dates des articles (format Unix) et faire un tri secondaire sur cette colonne... Mais, pour l’instant, je ne vois pas de façon simple sans fourrer du PHP partout !

      Je vais essayer d’y réfléchir ! Il doit bien y avoir un moyen de faire ça tout en Spip... J’en suis sûr...

    Répondre à ce message

  • 4
    Thierry Gagnon

    Merci beaucoup pour cette contrib que j’attendais depuis des années ! :)

    Par contre, j’ai eu beaucoup de difficulté à implémenter ce code pour les raisons suivantes :

    1) Il ne m’a pas été évident du premier coup quelles noisette je devais utiliser ni la façon dont elles devaient d’enchaîner.

    2) Plusieurs retours à la ligne ont été ommis à la fin de certains commentaires. Par exemple, la dernière ligne de code PHP :

    # on passe donc au suivant } };
    devrait se lire :

    # on passe donc au suivant 
    } };

    3) Pour une raison qui m’échappe (problème d’encodage d’un caractère ?) le commentaire « #Puis pour chaque id_article » provoquait une erreur « Parse error ». Retirer cette ligne a fait disparaître l’erreur !!

    4) Le commentaire « [(#Pour chaque article qui a ce mot clé] » devrait se lire "[(#REM)Pour chaque article qui a ce mot clé]

    5) Je suggère d’ajouter un commentaire dans le code pour indiquer le nom de la boucle contexte à remplacer. Par exemple : [(#REM) Remplacer "_Contexte_Article" par le non de la boucle contexte de l'article].

    Je suggère donc de corriger et tester le code et ensuide de fournir un fichier HTML à télécharger pour éviter toute confusion et d’assurer l’intégrité du code.

    Voici mon code final, au cas où ça pourra en aider certains :

    <BOUCLE_Mots_De_Article_Contexte(MOTS){id_article}>[(#REM) Pour chaque mot clé de l'article]
    	<BOUCLE_Tableau_Articles_Connexes(ARTICLES){!id_article}{id_mot}>[(#REM)Pour chaque article qui a ce mot clé]
    <?php
    # Un peu de PHP.
    # on incrémente le compteur de pertinence:
    # $pertinence est un tableau qui contient pour chaque id_article
    # un compteur du nombre de mots-clés communs
    $pertinence['[(#ID_ARTICLE|texte_script)]']++;
    ?>
    	</BOUCLE_Tableau_Articles_Connexes>
    </BOUCLE_Mots_De_Article_Contexte>
    
    <?php
    # Un peu de PHP.
    # on trie le tableau $pertinence[ ] afin que
    # les id_articles qui ont le plus de mots-clés
    # communs avec l'article du contexte soient en 1er
    arsort ($pertinence);
    $i=0;
    $maxarticles=20;
    $maxcommun=4;
    while (list($ks,$vs) = each($pertinence)) {
    #$ks contient id_article, $vs sa pertinence 
    if ($vs > maxcommun-1) {
    $i++; 
    if ($i==maxarticles) break;
    ?>
    <ul>
    <BOUCLE_Articles_Connexes(ARTICLES)>[(#REM) Liste absolument tous
    les articles]
    <?php
    # Un peu de PHP.
    # Vérifie si l'article est celui du tableau
    # Si oui, on effectue les actions de la boucle, sinon
    # on passe à l'article suivant.
    # ce petit morceau de PHP est en fait une condition de boucle
    # comparable à {id_article=$ks} qui ne fonctionne pas dans Spip 
    if ('[(#ID_ARTICLE|texte_script)]'==$ks) {
    ?> <li> <a href="#URL_ARTICLE"
    [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a>
    <B_Mots_Communs> Nombre de mots-clés communs&nbsp;:
    [(#_Mots_Communs:TOTAL_BOUCLE)]. (Liste des mots-clés
    communs&nbsp;:
    [(#REM) Remplacer "_Contexte_Article" par le non de la boucle contexte de l'article]
    <BOUCLE_Mots_Communs(MOTS){id_article=#_Contexte_Article:ID_ARTICLE}{id_article}{id_mot!=35}{par
    type}{par titre}{"&nbsp;- "}> <a href="#URL_MOT"
    [title="(#DESCRIPTIF|textebrut|couper{200})"]>[(#TITRE|supprimer_numero)]</a>
    </BOUCLE_Mots_Communs> ) </li>
    <?php
    # Un peu de PHP.
    # fin de la condition {id_article=$ks}
    # on passe donc à l'article suivant 
    }
    ?>
    </BOUCLE_Articles_Connexes>
    <?php
    # Un peu de PHP.
    # fin de cet id_article ($ks)
    # on passe donc au suivant 
    } };
    ?>
    </ul>
    • Thierry Gagnon

      Il y a aussi cette corretcion à appliquer :

      if ($vs > maxcommun-1) {
      $i++; 
      echo $i;
      if ($i==maxarticles) break;

      doit être

      if ($vs > $maxcommun-1) {
      $i++; 
      echo $i;
      if ($i==$maxarticles) break;

      (Il manque le signe $ aux variables.)

    • Patrice

      Bonjour,

      J’ai un p’tit blème quand mon article n’a pas de mots clés 2 lignes d’erreurs s’affichent :

      Warning: arsort() expects parameter 1 to be array, null given in /home.8/p/a/t/patcatna/www/ecrire/public.php(173) : eval()'d code on line 253
      Warning: Variable passed to each() is not an array or object in /home.8/p/a/t/patcatna/www/ecrire/public.php(173) : eval()'d code on line 257

      Cela semble venir de :

      if ($vs > maxcommun-1) {
      $i++;
      if ($i==maxarticles) break;

      En faisant les corrections du post ci-dessus c pire (déclaration des variables...)

      Merci de votre aide

      Patrice

    • Super contrib que je viens de mettre en place sur spip 1.9.2c.

      Ca fonctionne nickel grâce aux commentaires de Thierry Gagnon.

      Merci à tous :-)

    • Bertrand

      Salut à tous,

      Moi aussi j’ai eu le même soucis d’avoir deux lignes d’erreur quand mon article ne possède pas de mots clés...

      Ce que j’ai fais pour empêcher ça est un peu bourrin mais fonctionnel en attendant de trouver mieux...

      Pour plus de clarté dans mon code, j’ai placé le code de Thierry Gagnon (avec la correction du $ qu’il a proposé dans le post suivant) dans un fichier nommé inc-connexes.html

      Et après j’ai placé mon inclusion de fichier dans une boucle qui teste la présence de mots clés attachés à l’article...

      <BOUCLE_presence_mots(MOTS){id_article}{0,1}>
              <INCLURE{fond=inc-connexes}{id_article}>
      </BOUCLE_presence_mots>

      La boucle ne renvoie rien si jamais il n’y a pas de mots clés, donc il n’y aura pas d’erreur en cas de non-présence de mots clés. Et le {0,1} permet de n’afficher qu’une fois les articles connexes si jamais il y a présence de plusieurs mots clés.

      Ça fonctionne. Je suis tout ouïe pour quelque chose de plus propre.

      De même, il y a comme un bug. En effet, la noisette affichera $maxarticles - 1 articles connexes.

      En tout cas, merci pour cette contrib qui est très utile et merci à Thierry pour les corrections apportées !

    Répondre à ce message

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom