Etendre #BuddyPress – partie 1 : apprivoiser le composant Activity

Publié le

par

Crédits Photo Laundry by Joost Nuijten, on Flickr

Je vous invite à me suivre dans un voyage au coeur du composant social de WordPress!

2012/05/06 Update : i tried to translate this tutorial in english (sorry if sometimes my english looks like ‘franglish’ !)

Download the pdf file

Préambule

Enorme Merci à l’équipe des core developpers du composant de réseau social de WordPress

BuddyPress est incontestablement mon extension WordPress préférée. « Social Networking in a box », tel est son claim, et cette promesse est bien au rendez-vous. De mon point de vue, une des dates les plus importantes est le 16/02/2010, date qui correspond à la sortie de sa version 1.2 qui a permis de ne plus avoir besoin de WordPress Mu pour l’installer. Depuis lors, que notre configuration WordPress soit multisite ou non, nous pouvons proposer à nos visiteurs un environnement propice à la constitution de communautés.

BuddyPress est un plugin très singulier : à tel point qu’on parle de « Développeurs BuddyPress » ! Je ne connais pas beaucoup de plugins pour lesquels on utilise un tel qualificatif pour définir les personnes qui se lancent dans la conception d’extensions à ce plugin. Et précisément ce qui le singularise, c’est la multitude de hooks (de type Action ou Filter) qui sont disponibles et permettent de l’enrichir de nos créations.

Depuis que je l’ai rencontré en Juillet 2010, je lui ai consacré 25 articles pour présenter des astuces ou des plugins de ma conception comme BP My Home, BP Show Friends, Bowe Codes ou encore BP Code Snippets. Aujourd’hui, mon propos n’est pas forcément de vous livrer un nouveau trick ou plugin (même s’il y en a un à télécharger !!). Il s’agit plutôt de vous donner envie de créer le(s) vôtre(s) et de rejoindre le hall of fame des concepteurs de plugin BuddyPress :).

Ainsi, je vous propose de me suivre le temps d’une série d’articles dans une expédition au coeur de BuddyPress. Ce premier article nous permettra de prendre connaissance du paysage et de concevoir un plugin portant sur ce qui est pour moi le composant Majeur de BuddyPress : les Activities.

Table des matières…

Let’s pack : préparons notre valise !
Landing! Admirons quelques secondes le panorama.
Conception de notre plugin
Mise en jambes!
Charger un javascript sur conditions
Exemple d’identification du meilleur hook
Une petite pause pour déguster des cookies
Ready! en route pour la “main street” de notre extension
Sachons faire preuve d’imagination et d’astuces
Petite récréation : faisons quelques loopings !
Un petit relooking s’impose..
Simplifions la vie du super Admin et ayons le souci d’effacer nos traces!!
Download du plugin temporaire..
Bilan d’étape : levons le nez du guidon 2s
Upgrade de notre plugin
Optimisons!
Capitalisons!
WPAjaxifions le reshare
Profitons des activity metas.
Vers l’infiniternational et au delà !
Evolution possible de la fonction reshare
Download du plugin final..

Let’s pack : préparons notre valise !

Concevoir un plugin BuddyPress nécessite de d’abord s’équiper. Voici ma trousse à outils :

  1. WordPress (fr) 3.3.2 et BuddyPress 1.5.5.
  2. La traduction française de BuddyPress (fichiers po et mo à coller dans le répertoire bp-languages de BuddyPress)
  3. Un serveur local Apache/MySql/Php, dans mon cas j’ai choisi MAMP.
  4. Un éditeur de texte, TextMate m’a conquis!
  5. Des favoris vers le codex de BuddyPress et plus particulièrement les conditional template tags.
  6. Un favori vers le codex de WordPress, bien entendu.
  7. Optionnel : une lecture rapide de la Prez que j’avais utilisée lors du WordPress Day d’Algérie, l’histoire de se rafraîchir la mémoire quant à la conception de plugins WordPress
  8. PoEdit pour l’internationalisation de notre plugin
  9. Sans oublier une bonne liste de lecture. En ce moment j’écoute RHCP 😉

De la même manière que le slide 18 de ma présentation WordPress Day Algérie insiste sur la précaution de ne pas modifier le coeur de WordPress, il est indispensable de respecter cette même précaution s’agissant du code source de BuddyPress. Au delà des risques pour sa stabilité, pensez mise à jour ! BuddyPress se met régulièrement à jour et si vous le modifiez, vous perdrez toutes vos modifications lors de son prochain upgrade.

Landing! Admirons quelques secondes le panorama.

BuddyPress est une extension qui s’exécute sur l’ensemble du réseau dans le cas des configs WordPress Multisites et ses vues sont disponibles sur le blog principal du network. Notons que depuis la version 1.5, BP crée à l’install des pages WordPress pour ses différents composants.

En surligné bleu, j’ai indiqué les tables et scripts qui seront utiles dans le cadre de notre plugin. Par manque de place, je n’ai pas détaillé le contenu du répertoire bp-themes qui contient le thème par défaut de BuddyPress. Nous nous intéresserons notamment à ses templates activity (entry.php et post-form.php).

Nous disposons d’une précieuse globale $bp qui stocke un certain nombre de variables très utiles que nous pourrons récupérer pour notre plugin. Pour l’utiliser, il suffit d’y faire référence au début de votre fonction de cette manière :

function mafonction(){
	global $bp;

	/* suite de votre fonction */
}

Voici trois des variables de $bp que j’utilise régulièrement :

  • $bp->groups->current_group->id : informe sur l’id du groupe affiché
  • $bp->displayed_user->id : informe sur l’id du membre affiché
  • $bp->loggedin_user->id : informe sur l’id du membre connecté

BuddyPress nous propose également la classe BP_Component pour concevoir nos propres composants, sa Group API pour ajouter très simplement des « apps » pour les groupes et un certain nombre de fonctions très intéressantes pour interagir avec ses différents composants (activités, notifications, groups, forums, profiles…). Je reviendrai sur ces éléments dans un prochain article :).

Conception de notre plugin

Le besoin : permettre au super admin depuis un groupe support de poster des activités qui seront automatiquement visibles sur l’intégralité des autres groupes de la communauté.

Commençons par constituer notre environnement de test : Créons 3 groupes dont l’un sera privé. On récupère l’identifiant du groupe public que nous voulons utiliser comme « support » : nous le stockerons en dur dans une constante de notre plugin dans un premier temps.

Dans le répertoire plugins de WordPress, nous créons un nouveau répertoire bp-ads-group-update dans lequel nous allons ajouter les répertoires js, css, images, includes et le fichier principal de notre plugin bp-ads-group-update.php. Editons ce dernier fichier pour faire comprendre à WordPress qu’il s’agit d’un plugin grâce à l’ajout de tags spécifiques à son header.

Tout comme BuddyPress, notre plugin s’exécutera sur l’ensemble du Network (tag Network fixé à true) en cas d’installation multisite. Cela évitera notamment qu’il soit affiché dans la liste des extensions disponibles des blogs fils si toutefois l’administrateur a autorisé cet affichage. Nous retrouvons notre constante BP_AGU_GROUP_SUPPORT_ID qui contient l’id de notre groupe public support (dans cet exemple 3). J’ai également ajouté un certain nombre de constantes pour plus facilement faire référence aux url des répertoires js, css et images. La fonction plugins_url() est très pratique car selon le protocole de votre site (http ou https), elle construit la bonne url.

Mise en jambes!

Depuis l’administration des plugins ou la network administration des plugins selon votre configuration, activons le plugin. Bien entendu, à cette étape de la partie rien ne se passe !! Si vous souhaitez connaître l’intégralité des variables contenues dans la global $bp, on peut s’amuser à ajouter une première fonction qui se jouera au moment où WordPress passera par le hook wp_head.

function bp_agu_dump_bp() {
	global $bp;
	?>

	<pre><?php var_dump($bp);?></pre>
	<?php
}
add_action('wp_head', 'bp_agu_dump_bp');

Si vous vous amusez à rafraîchir le front de votre blog, vous obtenez le nec plus ultra du design ;). Plus sérieusement, le tout début du code illustre un exemple d’interception d’un hook action.

En utilisant la fonction add_action(‘hook_a_intercepter’, ‘par_ma_fonction’, 9, 1), nous indiquons à WordPress que dés qu’il passe par le ‘hook_a_intercepter’ alors il doit jouer ‘par_ma_fonction’. Les deux arguments qui suivent servent à indiquer la priorité d’exécution de notre fonction et le nombre d’arguments qui sont à récupérer du ‘hook_a_intercepter’. Supprimons cette fonction qui ne nous sera pas utile pour la suite..

Charger un javascript sur conditions

Dans notre trousse à outils, munissons nous de notre favori « conditional template tags ». Dans la mesure du possible, pour votre plugin, je vous invite à toujours faire en sorte de charger vos javascripts ou feuilles de style uniquement au moment où vous en avez besoin. En effet, notre besoin précise que les messages de support seront écrits depuis le groupe support. Dans le composant groupe, le template activity/post-form.php est uniquement chargé depuis l’accueil du groupe, ce qui correspond au conditional template tags bp_is_group_home().

function bp_agu_load_js() {
	global $bp;

	if ( bp_is_group_home() && $bp->groups->current_group->id == BP_AGU_GROUP_SUPPORT_ID ) {
		wp_enqueue_script( 'bp-agu-js', BP_AGU_PLUGIN_URL_JS . '/bp-agu.js', array('jquery') );
	}
}
add_action( 'bp_screens', 'bp_agu_load_js' );

Voici une nouvelle fonction WordPress très utile : wp_enqueue_script(‘identifiant_de_mon_js’, ‘url_de_mon_js’, array(‘scripts_de_dependance’)). WordPress recommande l’utilisation de cette fonction pour inclure nos scripts, un avantage est qu’elle permet d’inclure une seule fois un script identifié et de gérer simplement ses dépendances (dans notre exemple jQuery). Pour information, pour inclure un style, vous pouvez utiliser wp_enqueue_style.

Pour charger ce javascript, il faut bien choisir son moment et s’assurer que toutes les variables que l’on va tester ont été initialisée. Ici j’utilise le hook 'bp_screens'. Il est localisé dans le script bp-core-hooks.php du répertoire bp-core de BuddyPress. C’est le hook qui sert à définir les templates à afficher pour les pages des composants BP. Si par exemple, j’avais choisi le hook plugins_loaded, rien ne se serait passé car la variable $bp->groups->current_group->id n’est pas encore initialisée.

En l’occurrence, comme nous n’avons pas créé le fichier javascript, rien ne se passera 🙂 Créons donc le fichier bp-agu.js dans le répertoire js de notre plugin. Si vous voulez tester, ajouter lui simplement ce contenu :

jQuery(document).ready(function($){alert('chargé!')});

Exemple d’identification du meilleur hook

On revient dans quelques instants à notre fichier js. Pour le moment, on peut facilement ajouter un checkbox au formulaire d’update d’actualité. Je vous invite à parcourir le template activity/post-form.php du thème bp-default de BuddyPress.

L’illustration ci-dessus nous propose 3 hooks. Si, vous testez les numéro 1 et 3, vous vous apercevrez que le positionnement de notre checkbox est soit trop haut, soit vraiment trop bas. Donc, nous allons utiliser le hook 'bp_activity_post_form_options'. Notre fonction d’ajout du champ checkbox sera donc :

function bp_agu_checkbox(){
	global $bp;

	if ( is_super_admin() && bp_is_group_home() && $bp->groups->current_group->id == BP_AGU_GROUP_SUPPORT_ID ) {
		?>
		<span id="bp-agu-cb">
			<input type="checkbox" value="1" name="_support_message" id="support_message">Support</input>
		</span>
		<?php
	}
}
add_action('bp_activity_post_form_options', 'bp_agu_checkbox');  

En passant, la fonction is_super_admin() renvoie true si l’utilisateur connecté est le Super Administrateur du Network ou l’administrateur si WordPress n’est pas configuré en multisite. Si vous testez côté front, vous vous apercevrez que votre case à cocher n’est pas juste à côté du bouton submit comme illustré plus haut. C’est normal, dans la mesure où nous enrichirons cette méthode grâce à jQuery dans la version temporaire de notre plugin.

Notre checkbox va nous servir à déclencher le comportement de partage du message support sur tous les groupes du réseau BuddyPress si elle est cochée. On ne sait jamais, toutes les updates de ce groupe ne seront pas forcément des messages support. Déjà les activity recording liés à la publication de nouveaux forums seraient sinon automatiquement partagées sur tous les groupes, et nous ne souhaitons pas ce comportement. Occupons-nous désormais d’intercepter l’activité pour tester la valeur de cette checkbox une fois le message publié.

La fonction responsable des enregistrements d’activité est bp_activity_add(). Elle est localisée dans le script bp-activity/bp-activity-functions.php de BuddyPress. A la fin de cette fonction, un hook nous permet d’intercepter le moment où elle sera publiée, il s’agit du marqueur do_action( 'bp_activity_add' ). Néanmoins, si on se positionne ici, nous aurons à tester les paramètres associés à cette fonction pour savoir s’il s’agit bien d’une activité de groupe. Il y a plus simple, dans le script bp-groups/bp-groups-functions.php, la fonction groups_post_update() nous permet de nous raccrocher au hook do_action( 'bp_groups_posted_update' ) nous garantissant que l’activité a été postée depuis un groupe.

// fonction temporaire pour les besoins du test..
function bp_agu_check_support( $content, $user_id, $group_id, $activity_id ) {
	if( $group_id == BP_AGU_GROUP_SUPPORT_ID && $_REQUEST['_support_message'] ) {
		wp_die( 'la valeur du checkbox est ' . $_REQUEST['_support_message'] );
	}
}
add_action( 'bp_groups_posted_update', 'bp_agu_check_support', 9, 4);

Vous avez vu en gras, j’ai mis un 4ème argument à mon add_action() : j’ai indiqué que je voulais récupérer les 4 arguments qui sont passés via la fonction do_action( 'bp_groups_posted_update' ). Ces 4 arguments, je les spécifie dans ma fonction bp_agu_check_support.

Je vous invite à tester cette fonction en deux temps : d’abord normalement, ensuite en désactivant le javascript de votre navigateur. Dans le premier cas, rien ne se passe, dans le deuxième cas, vous avez bien une belle erreur WordPress affichée avec la valeur de notre checkbox. BuddyPress utilise régulièrement des requêtes AJAX pour ajouter en dynamisme, il faut donc procéder autrement pour passer la variable contenue dans notre checkbox, tout en envisageant le cas où l’utilisateur a désactivé le javascript de son navigateur.

Une petite pause pour déguster des cookies

Pour passer certaines de ses variables en AJAX, BuddyPress utilise des cookies qui dure le temps de votre session. Si vous vous amusez à afficher l’inspecteur de votre navigateur lorsque vous avez filtré les activités pour ne retenir que les updates, vous verrez un cookie du nom de bp-activity-filter dont la valeur est fixée à activity_update. D’ailleurs, c’est ce qui explique également que si vous faites un tour sur les actualités d’un de vos groupes ou sur les actualités de votre profil, elles seront elles-aussi filtrées sur les updates uniquement.

Nous allons donc profiter de ce mécanisme pour passer la valeur de notre checkbox dans la fonction AJAX qui s’occupe de poster les activités. Regardez l’extrait du fichier _inc/global.js du thème par défaut de BuddyPress, et plus particulièrement la ligne 67 :

Génial !! A chaque activité postée en AJAX, BuddyPress encode tous les cookies de session présents et les balance dans la variable $_POST['cookie']. Il ne nous reste plus qu’à écrire un cookie que nous appellerons bp-agu-is-support au moment où notre checkbox sera cochée. Et pour ce faire, nous allons éditer notre fichier javascript qui ne faisait qu’alerter « chargé! » pour le moment. Et pendant qu’on y est, on va récupérer notre checkbox pour la déplacer juste à côté du bouton submit :). Voici donc notre tout nouveau tout beau bp-agu.js :

jQuery(document).ready(function($){
	$('#whats-new-submit').prepend($('#bp-agu-cb').html());
	$('#bp-agu-cb').html("");

	if( $.cookie("bp-agu-is-support") == 1 ) {
		$("#support_message").attr('checked', 'true');
	}

	$('#support_message').live('click', function(){
		if( $(this).attr('checked') ) {
			$.cookie("bp-agu-is-support", $(this).val());
		} else { 
			$.cookie("bp-agu-is-support", '' );
		}
	} );
} );

En bleu gras, le moment où on affecte la valeur de notre checkbox à notre cookie. Maintenant, il faudra tester l’existence de ce cookie pour indiquer au super Admin qu’il va où non poster un message support. C’est la raison pour laquelle, on commence par d’abord tester cette existence et activer le cas échéant la checkbox. Maintenant que le javascript est en place, nous pouvons nous occuper de notre fonction bp_agu_check_support pour lui faire faire autre chose que renvoyer une erreur WordPress !

Ready! en route pour la « main street » de notre extension

Pour retrouver le code de cette fonction et le copier coller dans le plugin que nous avons démarré, vous pouvez à tout moment récupérer les sources. Maintenant analysons les 3 étapes de cette fonction. D’abord, nous récupérons la valeur de notre checkbox (étape 1).

Nous commençons par imaginer que l’utilisateur a désactivé le javascript de son navigateur en initialisant la variable $is_support_message par la valeur de la checkbox postée. Si elle n’existe pas, c’est que javascript est activé. Il s’agit donc de parser $_POST['cookie'] pour stocker tous les cookies du document dans un tableau associatif. Ensuite, nous récupérons tranquillement la valeur de notre cookie bp-agu-is-support pour l’affecter à notre variable $is_support_message.

Passons à l’étape 2 : si notre cookie est set et que l’id du groupe – qui nous a été passé en paramètres de notre fonction via les arguments du marqueur do_action( 'bp_groups_posted_update' ) – correspond à celui de notre groupe support (que nous avons défini en constante au début de notre plugin), alors nous allons récupérer les éléments de l’activité via la fonction bp_activity_get_specific() qui est localisée dans le script bp-activity/bp-activity-functions.php de BuddyPress. Comme son nom l’indique, elle permet de récupérer des activités en lui passant soit un id d’activité soit un tableau d’id d’activités. De la même manière que nous avons hérité du group_id du hook intercepté, on a cet id dans l’argument $activity_id de notre fonction. Il ne nous reste plus qu’à préparer le tableau des arguments qu’attend la fonction groups_record_activity() que vous retrouverez dans le script bp-groups/bp-groups-activity.php. On va simplement omettre intentionnellement de préciser l’index ‘item_id’ de ce tableau pour le moment. Détail important, notez bien qu’à la ligne 75 nous créons un nouveau type d’activité : support_update

Terminons par l’étape 3. Rappelons nous du besoin du super Admin : permettre au super admin depuis un groupe support de poster des activités qui seront automatiquement visibles sur l’intégralité des autres groupes de la communauté. Pour y parvenir, une boucle s’impose 🙂 C’est ce que nous allons faire en utilisant une classe WordPress hyper importante pour la communication avec la base de données : $wpdb. En passant, vous avez remarqué que je l’ai référencée au tout début du code (ligne 56) de cette fonction ? Pour un rapide brief sur cette classe, je vous invite à lire les slides 11 à 15 de la présentation qui m’avait servie lors du WordPress day d’Algérie. Nous récupérons donc un tableau contenant les ids de tous les groupes du site (table wp_bp_groups), sur lequel nous bouclons pour ajouter le fameux index ‘item_id’ – en vérifiant bien que l’id du groupe n’est pas celui de notre groupe support – au tableau des arguments attendus par la fonction groups_record_activity() et toujours dans la boucle on la joue. Résultat : on a dupliqué l’activité sur nos deux groupes.

Sachons faire preuve d’imagination et d’astuces

Alors, vous allez me dire « mouai pas terrible car si je vais sur les activités globales du site je vais avoir 3 fois la même actualité qui sera affichée ». Sauf que, dans le tableau des arguments de groups_record_activity(), nous avons précisé qu’il fallait précisément masquer ces activités ('hide_sitewide' => 1) :).

Vous n’avez pas pu attendre pour tester, moi non plus !! J’ai d’abord vérifié les activités globales de BuddyPress : et j’ai bien un seul message support, c’est normal car nous avons spécifié ('hide_sitewide' => 1). Si je vais sur le groupe privé que nous avons créé pour nos tests, c’est bien apparent. En revanche, lorsque je vais sur le groupe public de notre environnement de test : wallou, nada !!

Alors Pourquoi tant de haine ?? Ça fait 10 minutes que vous lisez ce tuto et « vlan lopamarché » ! En fait c’est normal que le groupe public n’affiche pas cette activité dans la mesure où dans la table wp_bp_activity le champ hide_sitewide de l’activité est fixé à 1 et que lui, étant groupe public, n’affichera que les actualités dont le champ hide_sitewide est fixé à 0. Alors, entrons un peu plus en profondeur dans le coeur du composant Activity de BuddyPress. la fonction groups_record_activity() du script bp-groups/bp-groups-activity.php appelle la fonction bp_activity_add() du script bp-activity/bp-activity-functions.php qui elle-même appelle la classe BP_Activity_Activity du script bp-activity/bp-activity-classes.php pour créer l’activité. Vous me suivez ?

La fonction get() de cette classe crée cet argument Sql WHERE "a.hide_sitewide = 0" si toutefois le groupe n’est pas privé : or nous avons besoin d’avoir les actualités dont le champ hide_sitewide est supérieur ou égal à 0 !! Je vous propose une pirouette pour atteindre cet objectif. Vous avez remarqué que la requête qui peuple $activities dispose d’un hook de type filtre ? On va donc pouvoir s’en servir. La particularité de ce type de hook est qu’il attend une valeur en retour. Nous allons donc utiliser un add_filter() en récupérant la valeur de la requête pour lui renvoyer une requête sensiblement différente : admirez l’astuce !

function bp_agu_sql_trick_to_include_hidden( $sql ) {
	f( bp_is_group_home() ) {
		return str_replace('a.hide_sitewide = 0', 'a.hide_sitewide >= 0', $sql);
	}

	return $sql;
}
add_filter('bp_activity_get_user_join_filter', 'bp_agu_sql_trick_to_include_hidden', 99, 1); /* do not forget the count activities sql !!! */ add_filter('bp_activity_total_activities_sql', 'bp_agu_sql_trick_to_include_hidden', 99, 1);

Ainsi, si nous sommes sur l’accueil du groupe, nous allons remplacer a.hide_sitewide = 0 par a.hide_sitewide >= 0 ; autrement, on retourne la requête sans modification. Comme pour le hook add_action(), en spécifiant après la priorité le nombre d’arguments attendu (ici 1), on passe la valeur de la requête SQL à notre fonction bp_agu_sql_trick_to_include_hidden($sql). Voilà qui va satisfaire notre plugin :).

Petite récréation : faisons quelques loopings !

C’est embarassant.. Tout n’est pas tout fini : si depuis les activités globales de notre site, j’active le tab My Groups ou si je vais sur mes activités de profil, étant super Admin, sont affichées les 3 activités. Alors, on pourrait se dire « c’est pas grave, ça ne touche que le super Admin !! », mais la maison essaye de soigner ses plugins. On va donc résoudre cette nouvelle difficulté. Pour cela nous allons faire connaissance avec le loop des activités de BuddyPress. Jetons un oeil au template activity/activity-loop.php du thème bp-default.

La fonction bp_has_activities() va très gentiment nous emmener découvrir le script bp-activity/bp-activity-template.php lequel contient notamment la classe BP_Activity_Template. bp_has_activities() appelle cette classe et stocke son rendu dans la globale $activities_template. Cette globale servira à BuddyPress pour retourner les différentes informations liées à l’activité à chaque fois que nous ferons appel aux template tags de l’activité. Si vous parcourez rapidement le template activity/entry.php du thème bp-default, vous allez rencontrer un certain nombre de ces template tags, en voici une sélection qui nous sera utile pour la suite :

  • bp_activity_can_comment(),
  • bp_activity_can_favorite(),
  • bp_activity_user_can_delete(),
  • bp_get_activity_type(),
  • bp_activity_user_link(),
  • bp_activity_avatar(),
  • etc..

Détail intéressant à propos de ces template tags, c’est qu’il marche souvent en paire. Je m’explique : bp_activity_avatar() affiche le résultat de bp_get_activity_avatar(). Et les bp_get_... contiennent tous des filtres à hooker :).

Revenons sur bp_has_activities(). Si on s’intéresse aux lignes 332 à 371 de son code, on s’aperçoit qu’en fonction du « scope », BuddyPress prépare des ajustements à la requête que nous avons vu plus haut. Ainsi, dans le cas des activités affichées dans le profil de l’utilisateur connecté, toutes les activités qu’il a partagées seront intégrées, il en va de même pour les activités du tab My Groups des activités globales. Et la fonction qui dirige cet aiguillage est à la fois dans l’encadré bleu de notre précédente illustration et à la fois (surtout d’ailleurs!) dans le script bp-core/bp-core-template.php, il s’agit de bp_ajax_querystring, et devinez quoi : cette fonction propose un filtre, nous allons donc pouvoir modifier ce comportement depuis notre plugin ;).

function bp_agu_neutralize_support_updates_in_scopes( $ajax_querystring, $object ) {
	$r = wp_parse_args( $ajax_querystring );
	extract( $r );

	if ( $scope == 'groups' || bp_is_user_activity() ) {
		$exclude_ids = array();
		$exclude_activities = bp_activity_get( array(
			'show_hidden'=>true,
			'filter' => array( 'action' => 'support_update' )
		) );

		foreach( $exclude_activities['activities'] as $exclude_id ) {
			$exclude_ids[] = $exclude_id->id;
		}

		return $ajax_querystring.'&exclude='.implode(',', $exclude_ids);
	}

	return $ajax_querystring;
}
add_filter( 'bp_ajax_querystring', 'bp_agu_neutralize_support_updates_in_scopes', 99, 2); 

Alors, pour ne pas afficher les activités en triplon, on va simplement les exclure si toutefois elles ont un type support_update. On a bien fait de créer ce type d’update dans notre fonction bp_agu_check_support()! Pour cela, on récupère les activités en question pour former un tableau et ajouter une variable à l’AJAX querystring qui contiendra une liste des ids des activités à exclure séparées par des virgules.

Un petit relooking s’impose..

Impressionnant ce qu’on peut faire avec les filtres, non ? Voici l’étendue du relooking pour nos support_updates affichées dans les activités des groupes :

  1. On neutralise les boutons d’action classiques (commenter, mettre en favoris ou supprimer).
  2. On change l’avatar de l’utilisateur par celui du groupe support (ainsi que le lien du profil utilisateur pour celui de l’accueil du groupe support).
  3. On supprime l’avatar secondaire, puisque nous n’en avons plus besoin.
  4. On modifie le permalien de l’activité pour celle du groupe support.
  5. On ajoute un bouton commentaire qui renvoie automatiquement sur l’activité « mère » qui a été créée par le groupe support, et on récupère par la même occase son nombre de commentaires.

Et toutes ces modifs en ajoutant des filtres aux template tags des activités que nous avons évoqués plus tôt. Je ne vais pas tous les illustrer, vous pourrez les retrouver en intégralité dans le fichier principal du plugin disponible en téléchargement, voici un exemple :

function bp_agu_override_activity_time_since( $content ) {
	if( 'support_update' != bp_get_activity_type() ) {
		return $content;
	}

	$parent_activity_id = bp_get_activity_secondary_item_id();

	return preg_replace( "//p/([0-9]+)//", '/p/'.$parent_activity_id.'/', $content );
}
add_filter( 'bp_activity_permalink', 'bp_agu_override_activity_time_since', 99, 1); 

Une fois le relooking achevé, les membres ne pourront pas commenter les ‘support_update’ mais seront dirigés sur l’activité « mère » créée par le groupe support : ce qui permet de centraliser les commentaires à un seul endroit. On désactive le bouton de suppression dans la mesure où on ne souhaite pas que les administrateurs des groupes puissent supprimer un message support. Enfin, on enlève le bouton de mise en favoris car qui souhaite bookmarker des messages supports ?!! Il nous reste à envisager la suppression de l’activité mère. Car si le super Admin supprime le message support, toute la logique tombe par terre : on perd la mère !!!! La fonction suivante se charge de cette éventualité en supprimant les activités filles si toutefois la maman était amenée à disparaître… Triste destinée pour ces filles, elles sont vouées à ne pas survivre à leurs parents 🙁

function bp_agu_handle_deleting( $args ) {
	bp_activity_delete( array(
		'type' => 'support_update',
		'secondary_item_id' => $args['id']
	) );
}
add_action('bp_activity_delete', 'bp_agu_handle_deleting', 9, 1 );

Simplifions la vie du super Admin et ayons le souci d’effacer nos traces!!

Voilà qui est pas mal, on peut presque constituer notre premier package pour tests ! Avant, il faut prévoir une interface pour notre plugin dans le backend de WordPress, l’histoire de ne pas l’obliger à modifier le code source du plugin pour adapter la constante BP_AGU_GROUP_SUPPORT_ID à l’id de son groupe support (qui n’est pas forcément 3!).

Comme nous concevons un plugin BuddyPress, je vous invite à rattacher cette page d’administration au menu BuddyPress du backend. En fonction de la configuration de WordPress, ce menu est soit sur l’espace WP Admin soit sur celui du Network Admin. Pour parer à toute éventualité, il s’agit d’utiliser la fonction is_multisite() et intercepter le bon hook en fonction de son résultat.

function bp_agu_backend_menu() {
	if ( !is_super_admin() ) {
		return false; 
	}

	add_submenu_page(
		'bp-general-settings',
		'Options Ads Group Update',
		'Options Ads Group Update' ,
		'manage_options', 
		'bp-agu-backend-slug',
		'bp_agu_backend_page'
	);
}
add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', 'bp_agu_backend_menu', 21); 

Pour en savoir plus sur l’ajout de page d’Administration WordPress, je vous invite à consulter (une nouvelle fois) la présentation que j’avais utilisée lors du WordPress Day d’Algérie et plus précisément son slide 36. En gras dans le code ci-dessus, la référence à la fonction qui sera jouée lorsque l’administrateur cliquera sur le sous menu « Options Ads Group Update » du menu BuddyPress

Si on s’intéresse au code source de la fonction bp_agu_backend_page(), on s’aperçoit qu’on utilise le mécanisme de sécurité wp_nonce pour s’assurer que la requête provient bien de notre site. Il est important de veiller également à valider les inputs utilisateurs avant de les stocker ou les afficher. Dans notre cas, comme on attend un ID de groupe, soit un entier, on utilise la fonction intval() pour s’assurer que seul un entier sera sauvegardé.

function bp_agu_backend_page() {
	if( $_POST['_bp_agu_options'] && check_admin_referer( 'bp-agu-option', '_bp_agu_option' ) ) {
		if ( update_option('_bp_agu_group_support', intval( $_POST['_bp_agu_group_support'] ) ) !== false ) {
			echo '<div id="message" class="updated"><p>Options sauvegardées</p></div>';
		}
	}

	$group_support = intval( get_option('_bp_agu_group_support') );

	// to be continued...
}

Nous utilisons donc la table wp_options pour stocker les réglages globaux de notre extension. Maintenant que l’admin peut stocker son groupe ID, il ne faut pas oublier de modifier la valeur de notre constante BP_AGU_GROUP_SUPPORT_ID !!

function bp_agu_define_support_group() {
	return intval( get_option('_bp_agu_group_support') );
}
define ( 'BP_AGU_GROUP_SUPPORT_ID', bp_agu_define_support_group() );

On a presque fini cette version beta de notre plugin : tenez bon !! Comme les fonctionnalités de notre plugin sont susceptibles d’évoluer, toujours dans la table wp_options de WordPress, il peut être utile de stocker sa version. Ainsi lors de mises à jour, nous pourrons le cas échéant prévoir des actions à réaliser en fonction de la version utilisée par le site. Pour cela, nous utiliserons le registration hook de WordPress qui intervient juste après l’activation de notre extension par l’administrateur.

function bp_agu_activate() {
	if ( get_option('_bp_agu_version') != BP_AGU_PLUGIN_VERSION ) {
		update_option( '_bp_agu_version', BP_AGU_PLUGIN_VERSION );
	}
}
register_activation_hook( __FILE__, 'bp_agu_activate' );

Enfin, vite fait : on va ajouter un nouveau fichier à la racine de notre plugin. On l’appelera uninstall.php. Ce fichier sera appelé par WordPress au moment où l’administrateur aura confirmé son souhait de supprimer le plugin de son site. C’est très important, dans la mesure du possible, de tenter d’effacer toutes les traces de notre plugin notamment dans la base de données. En plus dans notre cas, on a mis en place un mécanisme qui duplique les activités. Dés que l’admin aura supprimé notre plugin… patatra ! tout ce que nous avons astucieusement masqué, réapparaîtra :(. Je vous laisse découvrir son contenu (suppression des options et des activités de type ‘support_update’) dans la première version de notre extension disponible ci-après.. Hip Hip Hip…

Bilan d’étape : levons le nez du guidon 2s

Notre plugin commence à ressembler à quelque chose. Si on vérifie l’illustration du panorama, nous avons bien rencontré la classe « CRUD » BP_Activity_Activity de la table wp_bp_activity, les fonctions d’ajout et de suppression d’activités ainsi que le templating avec la classe BP_Activity_Template et les template tags filtrables. Il y a un point que nous n’avons pas abordé : ce sont les activity metas qui sont stockées dans la table wp_bp_activity_meta. On va faire connaissances avec elles dans notre prochaine étape.

Notre plugin reste perfectible : il est important de penser à l’internationaliser. Cela augmentera son utilisation et enrichira les feedbacks et demandes d’évolution. Il est important également de l’optimiser en s’assurant que BuddyPress est « ready to rock » avant de charger l’intégralité du code de notre extension.

En passant, la fonction de duplication des activités que nous avons utilisée pour diffuser des messages support me fait immédiatement penser aux tweets sponsorisés, c’est sans doute pour cette raison que j’ai appelé ce plugin BP Ads Group Update..

Upgrade de notre plugin

Evolution du besoin : astucieux ce principe de duplication des activités, du coup ça serait bien si les membres pouvaient repartager des activités..

Optimisons!

Ça tombe bien, dans la fonction de reshare on peut imaginer l’intérêt d’ajouter un compteur et donc d’utiliser les activity metas 😉 Avant de partir tête baissée dans la conception de cette nouvelle fonctionnalité, réorganisons notre plugin.

Comme vous pouvez le constater, on a créé 3 nouveaux fichiers que nous avons rangés dans le répertoire includes de notre plugin. Toutes les fonctions liées à la fonctionnalité support_update ont été déplacées dans le script includes/bp-agu-support.php, nous avons préparé le fichier includes/bp-agu-reshare.php pour accueillir les fonctions liées à la fonctionnalité reshare_update et nous avons déplacé les fonctions liées à l’interface d’administration de notre plugin dans includes/bp-agu-admin.php.

S’agissant de la fonction bp-agu-init(), elle se déclenche au moment ou BuddyPress passe par le hook bp_include et commence par inclure le fichier includes/bp-agu-support.php avant de charger si les conditions sont réunies (fonction reshare activée ou interface admin) les autres scripts. En procédant ainsi, nous allons pouvoir capitaliser sur le code support_update. Si vous regardez la fonction bp_agu_reshare_is_activated(), elle se charge tout simplement de retourner le choix que nous avons fait dans l’interface d’administration de notre plugin. Il s’agit donc dans un premier temps de modifier notre code pour proposer au super Admin un radio d’activation-désactivation de la fonction reshare.

Je ne détaille pas le code ici, vous pourrez le consulter dans le téléchargement de la version finale. Nous décidons pour pouvoir mieux identifier nos « retweets » d’utiliser un nouveau type d’activité : reshare_update.

Capitalisons!

Pour permettre au membre de repartager une activité, on va simplement ajouter un nouveau bouton d’action sous l’activité et ce de la même manière dont nous avions procédé pour ajouter un lien vers les commentaires de l’activité parente support au moment de notre relooking (point 5) D’ailleurs, on va en profiter de ce bouton qui récupère le nombre de commentaires de l’activité parente et nous renvoie vers son permalien. Je pense que les commentaires seront bien mieux dans l’activité premièrement paratagée.

Pour cela dans le script includes/bp-agu-support.php, il s’agit de donner une priorité plus importante au hook qui rajoute ce bouton qu’à celui que nous allons créer pour notre bouton « reshare ». Par ailleurs, il suffira de rajouter l’activité reshare_update à la condition qui crée ce bouton commentaire.

/* dans le fichier bp-agu-support.php */
function bp_agu_add_action_link(){
	if( ! in_array( bp_get_activity_type(), array('support_update', 'reshare_update') ) ) {
		return false; 
	}

	/* suite de la fonction */
}
// hook avec une priorité 9
add_action('bp_activity_entry_meta', 'bp_agu_add_action_link', 9 );

/* dans le fichier bp-agu-reshare.php */
function bp_agu_add_reshare_button() {
	global $bp;

	if( ! is_user_logged_in() ) {
		return false;
	}

	/**
	 * on garde toutes les activités sauf support, à part.. * si vous voyez un intérêt à repartager les messages de service
	 */
	if( 'support_update' == bp_get_activity_type() ) {
		return false;
	}

	/* suite de la fonction */
}
// hook avec une priorité 10, le bouton reshare sera affiché après le bouton commentaire
add_action('bp_activity_entry_meta', 'bp_agu_add_reshare_button', 10 );

En gras la condition « si pas dans le tableau » nous servira pour également profiter des points 1 et 4 du relooking des activités support. Revenons à notre bouton « reshare ». Dans notre fonction bp_agu_add_reshare_button(), nous allons créer un lien contenant une variable get ‘to_reshare’ à laquelle on va ajouter un nonce pour plus de sécurité. Ce lien nous servira si toutefois l’utilisateur a désactivé le javascript de son navigateur. Car pour ajouter en dynamisme, nous allons utiliser AJAX par défaut! Ensuite, ce bouton sera cliquable si des conditions sont remplies :

  1. L’auteur d’une activité ne pourra par la repartager : aucun intérêt !
  2. Si toutefois le membre a déjà partagé une activité, il ne pourra pas la repartager : nous aurons donc en plus du compteur de « reshares », une deuxième activity meta qui stockera un tableau des user_ids ayant « retweeté »

WPAjaxifions le reshare

Voilà un nouveau mécanisme WordPress très intéressant. Pour l’utiliser, il faut procéder en 3 étapes :

  • Côté client (PHP -> JS) : ajouter une variable javascript pour indiquer l’url d’envoi des requêtes ajax.
  • Côté client (JS) : prévoir une variable dont le nom sera ‘action‘ dans notre jQuery.post.
  • Côté serveur (PHP) : ajouter un hook `wp_ajax` pour intercepter cette variable ‘action‘ passée et renvoyer une réponse avant de stopper l’exécution du code PHP.

S’agissant du premier point, dans l’environnement BuddyPress, la variable « ajaxurl » est de toute façon dispo. Donc, next! On s’occupe de notre javascript. N’oublions pas de l’ajouter à la page avec wp_euqueue_script bien entendu, vous vous souvenez comment on fait ? Ce coup-ci, on n’a pas seulement besoin que notre javascript soit chargé pour l’activité d’un seul groupe, mais pour toutes les activités. La condition sera donc :

function bp_agu_load_reshare_css_js(){
	if( bp_is_activity_component() || bp_is_group_home() ) {
		wp_enqueue_style('bp-agu-reshare-css', BP_AGU_PLUGIN_URL_CSS .'/reshare.css');
		wp_enqueue_script('bp-agu-reshare-js', BP_AGU_PLUGIN_URL_JS .'/reshare.js', array('jquery'), 0, 1);
	}
}
add_action('bp_actions', 'bp_agu_load_reshare_css_js');

reshare.js intercepte le click sur le bouton de reshare, envoie la requête ajax qui est interceptée par la fonction bp_agu_handle_ajax_reshare() du script bp-agu-reshare.php. Une fois la réponse reçue, on modifie l’affichage du bouton et on incrémente de 1 le nombre de « repartages ». Enfin, il ne faut pas oublier de renvoyer false pour éviter que le lien ne soit effectivement soumis.

Profitons des activity metas.

Dans l’illustration précédente, la fonction bp_agu_prepare_reshare() est chargée de constituer le tableau des arguments nécessaires à l’enregistrement de l’activité de type reshare_update par la fonction BuddyPress bp_activity_add(). Pour ce faire on lui passe l’id de l’activité à partager que l’Ajax nous a transmis et c’est à ce moment de la partie que les activity metas entrent en scène. Dans le script bp-activity/bp-activity-functions.php, on découvre les 3 outils qui nous permettent d’interagir avec la table wp_bp_activity_meta.

  • bp_activity_update_meta( $activity_id, $meta_key, $meta_value )
    On ajoute ou on modifie l’information stockée pour une meta key d’un id d’activité.
  • bp_activity_get_meta( $activity_id = 0, $meta_key = "" )
    On récupère l’information stockée pour la meta key d’un id d’activité.
  • bp_activity_delete_meta( $activity_id, $meta_key = "", $meta_value = "" )
    On supprime l’information stockée pour la meta key de l’id d’activité.

Ce qui est intéressant, c’est que les valeurs sont sérialisées/désérialisées automatiquement en faisant appel respectivement à bp_activity_update_meta() et bp_activity_get_meta(). Ce qui nous fait gagner du temps pour stocker des arrays PHP par exemple 🙂

Tout d’abord, on récupère les éléments qui composent l’activité à partager. Son contenu et sa visibilité (hide_sitewide) nous serviront pour notre reshare. Son id deviendra notre secondary id : c’est cette donnée qui nous permettra de créer un lien de filiation entre le reshare et l’activité premièrement postée.

Etape 2 : on récupère la meta « compteur de reshares » attachée à l’activité à partager (la parente), si elle existe on incrémente de 1 sinon on initialise à 1 avant de mettre à jour cette meta. On s’occupe après du tableau des user_ids ayant « reshared » l’activité, en lui ajoutant l’id du membre connecté s’il n’est pas déjà présent où si toutefois le tableau n’existe pas on le crée en le remplissant de l’identifiant de notre membre. On met à jour cette deuxième meta.

Enfin, on compose les arguments de notre nouvelle activité avant de les retourner. Vous avez vu j’ai ajouté un filtre si toutefois un plugin tiers ou le functions.php d’un thème voulait l’intercepter ;). Notons que cette fonction est également appelée dans le cas où le navigateur a son javascript désactivé. Dans ce dernier cas, il suffit de créer une fonction de fallback qui interceptera le hook bp_actions et si la variable $_GET['to_reshare'] n’est pas vide alors, on ajoute l’activité, on en profite pour prévoir d’informer l’utilisateur du succès ou non de cette action via la fonction bp_core_add_message() avant de rediriger l’utilisateur sur la même page épurée des variables. Vous trouverez le code pour cette fonction (bp_agu_handle_nojs_reshare() ) de callback dans le plugin en téléchargement plus bas à la ligne 148 du script includes/bp-agu-reshare.php

On a presque fini !! A l’instar de ce qu’on a fait pour la fonctionnalité de messages support, nous n’oublierons pas de créer la fonction qui supprimera les activités de type reshare_update si toutefois l’activité principale avait disparu et on veillera à adapter notre uninstall.php.

Vers l’infiniternational et au delà !

Permettre à son plugin d’être traduit est de mon point de vue très important car cela maximisera son utilisation au delà de nos frontières (y’en a encore ??) niveau mondial !! Pour cela WordPress utilise la librairie gettext, vous trouverez dans le codex toutes les fonctions de traduction disponibles.

Dans notre plugin, à chaque fois où on a indiqué des messages destinés à être affiché en français, il suffit de les traduire en anglais en les incluant dans des fonctions du type :

  1. __( 'Translate!', 'identifiant_de_notre_plugin') pour retourner la traduction dans une variable
  2. _e( 'Translate!', 'identifiant_de_notre_plugin' ) pour afficher la traduction dans le browser

Nous référençons notre « plugin_textdomain » en l’accrochant à un hook intervenant suffisamment tôt dans le chargement de BuddyPress (exemple sur fond noir dans l’illustration ci-dessus). Nous constituons un fichier .pot qui précisera les numéros de ligne du fichier avec les éléments à traduire pour chaque et nous terminons en générant les catalogues de langue (fichiers fr_FR po et mo) à l’aide de PoEdit.

Evolution possible de la fonction reshare

Pour les besoins du tutoriel sur les activity metas, nous stockons les user_ids des membres ayant repartagé une activité dans un tableau attaché à l’activité partagée. Or, cette information, étant fortement liée à l’utilisateur serait sans doute mieux dans les user_metas. Ainsi, nous pourrions plus facilement créer un nouvel environnement pour les membres connectés sur le modèle de la vue « My favorites ». On pourrait l’appeler « My reshares ». Dans le user_meta de l’utilisateur connecté, on stockerait un tableau des ids de ses activités de type reshare_update, on aurait plus qu’à utiliser la fonction bp_activity_get_specific() en lui passant ce tableau pour lister les reshares 😉

La version finale !!

Crédit photo : Web414 Demands Your Applause! by Pete Prodoehl, on Flickr

Vous êtes arrivé jusqu’ici ? Enorme Bravo à vous !! Je reconnais que ce premier tutoriel est relativement long. J’ai essayé de vous retranscrire le plus fidèlement possible le résultat de mes explorations de BuddyPress. Il y a certainement des éléments perfectibles mais je pense que nous avons des bases intéressantes pour passer à une étape supérieure : la conception d’un composant BuddyPress à l’aide de la classe BP_Component… A venir, très prochainement sur ce blog 😉

17 réponses à “Etendre #BuddyPress – partie 1 : apprivoiser le composant Activity”

  1. Avatar de BenHen

    Une nouvelle fois merci et bravo pour ce super tuto!

    2 plugins en bonus qui étendent les possibilités de Buddypress.

    Super intelligent les pirouettes pour ne pas afficher les activités plusieurs fois!!

    1. Avatar de imath

      Merci pour ton commentaire et ton feedback BenHen 🙂

  2. […] по имени @imath написал первую часть подобного мануала на французском языке, что уже само по себе […]

  3. Avatar de Wcee
    Wcee

    Good job Imath! (comme toujours)

    Excellent tuto, très sharp. Très hâte à la 2ieme partie, en espérant que tu nous apprendra comment faire évoluer les reshare (excellente fonction afin de ne pas subtliser les idées des autres mais bien de les repartager).

    C’est pour quand la part 2…?

    1. Avatar de imath

      Hello, pour la 2e partie, j’ai plutôt prévu de m’appuyer sur bp-checkins pour montrer comment utiliser la classe BP_Component pour créer son composant BuddyPress. Ce qui en plus me permettra de passer ce plugin d’une version beta à une 1.0 😉
      Merci pour ton commentaire & feedback 🙂

  4. Avatar de josemv

    WOW.
    Je souhaite de practique mon francais (need to undust my french)

    Mon ami, you rock !

    PD: I know, I know there’s a translated PDF on top of this post

  5. Avatar de imath

    Hello @ JOSEMV

    Thanks for your feedback and interest in all my experiments 🙂

  6. Avatar de Wcee
    Wcee

    Salut iMath,

    Serais-tu en mesure de me dire comment afficher mes reshare sur une nouvelle page de membre « Mes Reshares » et aussi d’ajouter un bouton « Supprimer » pour chacun de mes reshares.

    Ça serait grandement apprécié…

    Merci à l’avance!
    Wcee

  7. Avatar de imath

    Salut @ Wcee

    C’est tout l’objet du paragraphe ‘Evolution possible de la fonction reshare’ en fait 😉

    Il faudrait modifier le code source en conséquence :
    – ids des activités partagés dans un user_meta au lieu des ids des users dans les activity_meta
    – après il suffit soit de hooker certaines actions soit de construire un composant BuddyPress (BuddyPress Skeleton component est un bon ‘boilerplate’ http://wordpress.org/extend/plugins/buddypress-skeleton-component/ ) pour ajouter la vue dans l’environnement des membres..

    Mais j’anticipe sur la partie 2 de cette série de tutos 😉 J’y reviendrai donc plus en détail très prochainement..

    A+

  8. […] Etendre #BuddyPress – partie 1 : apprivoiser le composant Activity […]

  9. Avatar de Jon
    Jon

    Great job with this plugin imath, could you point me in the right direction of adding more structure to the re share template?I want to be able to re share my custom post type along with its main picture and title/ description.

    I’ve been playing around for a while now and can only get a short excerpt of the post.

    Any help would be great!

    Thankyou

  10. Avatar de imath

    Hi @ Jon,

    If you look at how BuddyPress record an activity when a new blog post is published, you’ll see that it creates an activity content with a thumbnail of an image if one is attached to post.
    If i use the reshare function, then it will take this content and you’ll have the image and description reshared.

    So i guess you first need (it will be easier) to record an activity once your custom post type is published on the same model than BuddyPress does for blog posts, and then if the activity is reshared its content will be reshared.

    Now if you want to do in an other way, then you’ll need to get the blog post id and re create the element you need :
    for example the post id is $activity->secondary_item_id in bp_agu_prepare_reshare function after line 88 of the bp-agu-reshare.php file.

    Hope it’ll help you.

  11. Avatar de Wcee
    Wcee

    Rebonjour iMath!
    Je voulais te demander si tu as l’intention de revenir pour la deuxième partie des reshares comme mentionné car j’aurais vraiment voulu pouvoir permettre aux membres d’avoir une section « Mes Reshares » dans « Activité » de leur compte ainsi que le bouton « Supprimer » en dessous de l’update d’activité reshare pour que les membres qui ont resharé une activité puissent aussi changer d’idée et effacer leur reshare. Ça serait vraiment super et je crois que ça serait un excellent avantage à avoir pour tout type de réseau social (ce que Twitter ne permet même pas!) 😉
    SVP!!!
    Merci et ne lâche pas le bon travail.

  12. Avatar de imath

    Salut @ Wcee

    Merci pour ton commentaire. J’ai eu une période compliquée, mais je suis de retour 😉

    Je regarde ça asap !

  13. […] de BuddyPress. A l’époque, j’avais notamment exploré la mise en place d’une fonctionnalité de repartage ou “reshare” des activités. A l’issue de ce tutoriel, j’avais introduit un “tome 2″ (je […]

  14. Avatar de iggOne

    Je vais tenter de développer pour la première fois un plugin pour développer mes activités BP.

    Ce ne sera pas simple mais c’est désormais possible grâce à ton tutoriel. Il me fait bien chauffer le cerveau :D.

    Merci beaucoup.

    1. Avatar de imath

      You’re welcome 😉 Happy coding !