WP Todolist – Développer une extension WordPress avec la POO – Partie 2
Dans une première partie, nous avons démarré une extension WordPress basée sur de la POO : WP Todolist. Nous avons créé notre premier custom post type afin de gérer nos todo. Cependant, une todo à différentes caractéristiques comme le fait d’avoir différents états (terminé, en cours, important, … ). Comment orienter notre développement pour répondre à ce besoin en programmation orientée objet ?
Actuellement, nous avons une todolist où l’on ne peut créer que des todo sans rien pouvoir en faire. Il serait intéressant maintenant de pouvoir les filtrer. Le but est d’avoir un aspect très dynamique au niveau de nos filtres afin d’être aussi flexible que ce que l’on pourrait faire avec Trello ! Une organisation de type : en test, en cours, en production par exemple ! Pour cela, nous allons devoir créer une taxonomy sur nos todo.
Pourquoi le choix de la taxonomy ?
Une taxonomy est une relation de type ManyToMany. C’est à dire qu’une taxonomy peut avoir un ou plusieurs custom post type et un custom post type peut appartenir à une ou plusieurs taxonomy. Or, l’état d’être en test ou en production pour une todo, c’est une relation ManyToOne. C’est à dire qu’un custom post type appartient à une et une seule taxonomy mais qu’une taxonomy peut avoir un ou plusieurs custom post type. On va donc devoir régler ce problème à la main dans ce tutoriel. Dans l’idéal, je vous conseillerais plutôt d’utiliser ACF puisqu’il le fait déjà bien ! Mais nous ne sommes pas là pour utiliser ACF (mais si vous voulez en savoir plus, je vous invite à lire notre article ACF pour les débutants).
Pourquoi ne pas choisir une meta donnée ?
Une meta donnée dans WordPress est une relation de type OneToOne avec notre custom post type. Ce serait donc une erreur conceptuelle d’enregistrer l’information que nous souhaitons traiter en tant que meta donnée.
DU CÔTÉ DE LA TAXONOMY : STATE
Je vais commencer par créer ma class Taxonomy que je vais nommer State.
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FTaxonomy%2FState.php%0Anamespace%20Todolist%5CWordPress%5CTaxonomy%3B%0A%0Ause%20Todolist%5CModels%5CHooksInterface%3B%0Ause%20Todolist%5CWordPress%5CHelpers%5CTaxonomy%3B%0Ause%20Todolist%5CWordPress%5CHelpers%5CPostType%3B%0A%0A%2F**%0A%20*%20State%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%20%3Ccontact%40wp-god.com%3E%0A%20*%20%40since%201.0.0%0A%20*%20%40version%201.0.0%0A%20*%2F%0Aclass%20State%20implements%20HooksInterface%20%7B%0A%0A%20%20%20%20public%20function%20hooks()%7B%0A%20%20%20%20%20%20%20%20add_action(%20%22init%22%2C%20array(%24this%2C%20’initTaxonomy’)%20)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20function%20initTaxonomy()%20%7B%0A%0A%20%20%20%20%20%20%20%20%24labels%20%3D%20array(%0A%20%20%20%20%20%20%20%20%20%20%20%20’name’%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20__(%20’State’%2C%20’td’%20)%2C%0A%20%20%20%20%20%20%20%20)%3B%0A%0A%20%20%20%20%20%20%20%20%24args%20%3D%20array(%0A%20%20%20%20%20%20%20%20%20%20%20%20’labels’%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%24labels%2C%0A%20%20%20%20%20%20%20%20)%3B%0A%0A%20%20%20%20%20%20%20%20register_taxonomy(%20Taxonomy%3A%3ASTATE%2C%20array(%20PostType%3A%3ACPT_TODO%20)%2C%20%24args%20)%3B%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%7D%0A%20%20%20%0A%7D%0A” message=”” highlight=”” provider=”manual”/]
Cette class n’a rien d’extraordinaire. On retrouve l’implémentation de HooksInterface afin d’obtenir la méthode hooks et de pouvoir être exécuté sur l’initialisation du plugin. Ensuite, cette même méthode contient une action sur le hook init afin de créer les taxonomies sur la méthode initTaxonomy. J’ai également utilisé une class “Helper” afin de mettre en constante le nom de la taxonomy ; comme nous l’avions fait pour notre custom post type.
[pastacode lang=”php” manual=”%3C%3Fphp%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FHelpers%2FTaxonomy.php%0A%0Anamespace%20Todolist%5CWordPress%5CHelpers%3B%0A%0A%2F**%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%0A%20*%20%40version%201.0.0%0A%20*%20%40since%201.0.0%0A%20*%2F%0Aabstract%20class%20Taxonomy%7B%0A%0A%20%20%20%20const%20STATE%20%3D%20%22td-state%22%3B%0A%0A%7D%0A%0A” message=”” highlight=”” provider=”manual”/]
Il nous reste à instancier notre class State (qu’il faut voir comme un service) lors de la création de nos actions. Rappelez-vous notre tableau : $actions se trouve dans notre fichier principal.
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F**%0A%20*%20Plugin%20Name%3A%20WP%20Todolist%0A%20*%20Description%3A%20Gestion%20d’une%20Todolist%20dans%20WordPress%0A%20*%20Version%3A%201.0.0%0A%20*%20Author%3A%20%3Ca%20href%3D%22http%3A%2F%2Ftwitter.com%2FTDeneulin%22%20target%3D%22_blank%22%3EThomas%20DENEULIN%3C%2Fa%3E%0A%20*%2F%0A%0Arequire_once(%20dirname(%20__FILE__%20)%20.%20’%2Fvendor%2Fautoload.php’%20)%3B%0A%0Ause%20Todolist%5CTodolist%3B%0Ause%20Todolist%5CWordPress%5CPostType%5CTodo%3B%0Ause%20Todolist%5CWordPress%5CTaxonomy%5CState%3B%0A%0A%24actions%20%3D%20array(%0A%20%20%20%20new%20Todo()%2C%0A%20%20%20%20new%20State()%0A)%3B%0A%0A%0A%24todolist%20%3D%20new%20Todolist(%24actions)%3B%0A%24todolist-%3Eexecute()%3B” message=”” highlight=”” provider=”manual”/]
Nous voilà maintenant avec notre taxonomy State qui va gérer l’état de nos todo. Maintenant, il faut nous attarder sur le fait de n’avoir qu’une seule taxonomy par custom post type mais avant ça…
Faisons une code review !
Oui ! ( Qu’est-ce qu’une code review ? ) Ça peut vous paraître un peu tôt car nous n’avons pas tant de code que ça. Cependant, je souhaite le faire rapidement pour vous sensibiliser au principe SOLID et au fait que nous pourrions déjà nous exclamer de quelques “WTF ?!” en regardant notre code… ! (la meilleur unité de mesure pour du code quality, je vous l’assure !)
Je me répète régulièrement en expliquant qu’une class doit être vue comme un service plus que comme simple objet qui nous permet de faire un package de méthodes bien organisées. Si l’on regarde d’un peu plus près notre class Todo (Todolist\WordPress\PostType\Todo) et notre class State (Todolist\WordPress\Taxonomy\State), nous débordons déjà !
En effet, elles font presque toutes les deux la même chose. L’une s’occupe de hooker la création d’un custom post type (CPT) et l’autre, la création d’une taxonomy. Maintenant, est-ce vraiment le rôle d’une class de ce type de connaître la procédure même de création d’un CPT ou d’une taxonomy ? Non.
Son seul et unique rôle serait de provoquer les hooks nécessaires à WordPress pour qu’une autre class s’occupe de créer en masse les taxonomies ou les custom post type…
Un scénario qui va nous éclaircir l’esprit
Dans mon plugin, je risque très fortement d’avoir besoin de plusieurs taxonomies (j’en suis même certain). En ce qui concerne les CPT, je ne sais pas trop mais sait-on jamais. De fait, pour faire bien faire les choses et continuer dans la lignée de notre développement, il faudrait :
- Créer une nouvelle class avec le nom de notre taxonomy
- Faire la méthode hooks pour avoir notre fonction callback qui va effectuer le register_taxonomy
- Instancier notre class à la racine de notre plugin et le tour est joué.
Même scénario pour un custom post type. C’est répétitif n’est-ce pas ? Mais c’est plutôt propre.. ! Cependant, imaginez que vous souhaitiez faire un changement quelconque lors de la création de ces taxonomies. N’importe lequel tant que celui-ci doit être généralisé. Ou bien, si WordPress rajoute des paramètres, change le nom de la fonction, … Bref, un changement générique ! Il va vous falloir que vous repassiez sur X class taxonomy et/ou X class custom post type. En terme de maintenabilité ce n’est pas agréable, c’est plus long à tester et sur la durée, ce n’est pas fiable. Ne peut-on pas faire plus simple ? Une “super méga” fonction qui fait tout d’un coup à l’ancienne ? On en est pas loin.. !
Ce principe de création, cette forme d’usinage : le design pattern Factory
Le problème de conception que je viens d’évoquer est déjà résolu depuis de très nombreuses années, c’est le design pattern Factory. Nous avons donc là une première dette technique qui s’est formée. Nous en sommes conscient et nous pourrions résoudre la problématique immédiatement. Mais, ce n’est pas le sujet du jour et nous nous devons d’être pragmatique. Une sur-optimisation n’a pas encore lieu d’être. Dans un contexte personnel et pour le plaisir, faites-le, c’est très enrichissant. En revanche, dans un contexte professionnel il y a plusieurs paramètres à prendre en compte :
- A-t’on le temps et l’argent nécessaire pour le faire ? (un client ne vous laisse pas le temps, n’a pas un budget conséquent pour vous permettre ce genre d’optimisation, … ? )
- Est-ce rentable ? (si je compte vivre de ce plugin, il faudrait d’abord penser à le commercialiser plutôt qu’à le sur-optimiser ?)
- Est-ce nécessaire au projet d’être le plus robuste possible tout de suite ?
Dans tous les cas, nous pouvons nous mettre une petite todo : “factoriser la création des taxonomies et des custom post type en implémentant le design pattern factory”.
Un et un seul état pour une todo
Avant d’écrire la première ligne de code, voyons d’abord de quelle manière nous pourrions aborder le problème afin de ne sélectionner un et un seul état pour une todo.
- À l’enregistrement d’une todo, on fait en sorte de bloquer le résultat à une case à cocher maximum. Pas de changement de design, juste une validation plus stricte.
- Une solution uniquement JavaScript ? Cependant, nous allons avoir les foudres des personnes qui rappellent que l’administration de WordPress est compatible en noscript. Il faudrait donc combiner cette solution à la première.
- Changer l’apparence des checkbox en bouton radio et de fait, avoir une validation d’un seul choix.
Nous n’allons pas faire les fainéants avec la solution JavaScript même si je l’aime beaucoup. N’étant pas un très grand partisan du noscript, j’aurai pu ne faire que cette solution en fonction du besoin. La première solution est aussi rapide mais pas très “user-friendly”. Soyons plutôt gourmand sur ce coup et changeons l’apparence des checkbox en bouton radio !
Apprenons de notre code review et ne pensons pas qu’à notre seule taxonomy State : généralisons la solution !
Pour cela, je veux donc l’équivalent d’une Factory qui va s’occuper de créer les boutons radio à la place des checkbox en fonction de la taxonomy que je vais lui envoyer. Avant de s’alarmer sur le mot Factory, je ne vais pas ressortir le modèle UML ni chercher à implémenter la définition même de la Factory puisque ce n’est pas possible dans notre cas. Ce patron de conception permet de retourner des instances d’objets. Or, nous allons nous servir uniquement de ce que je vais appeler “la logique de fond” du design pattern qui a pour but, au final, de créer quelque chose.
Avant de se lancer dans les explications, je vous ai schématisé la logique que l’on va appliquer afin d’utiliser convenablement notre factory. Ce sera notre ligne directrice pour savoir les class que nous allons devoir utiliser.
Nous allons donc avoir 3 class pour implémenter cette logique :
- RadioTaxonomy : c’est un service qui va effectuer l’intermédiaire entre notre demande, qu’est de transformer les boutons des taxonomies en checkbox et, la factory qui est capable d’effectuer cette modification.
- RadioTaxonomyFactory : notre factory qui va générer les boutons radios. Elle ne va pas contenir toute la logique de création car c’est trop lourd pour cette class. En revanche, elle va se servir d’un handler qui contiendra toute la logique nécessaire aux boutons radios.
- RadioTaxonomyHandler : Ce handler contient toute la logique nécessaire aux boutons radios.
Rappel : on donne à une class ce dont elle a besoin pour fonctionner
Place au code afin d’avoir une meilleur visibilité à toutes ces explications.
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FServices%2FRadioTaxonomy.php%0Anamespace%20Todolist%5CWordPress%5CServices%3B%0A%0Ause%20Todolist%5CModels%5CHooksInterface%3B%0A%0A%2F**%0A%20*%20RadioTaxonomy%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%20%3Ccontact%40wp-god.com%3E%0A%20*%20%40since%201.0.0%0A%20*%20%40version%201.0.0%0A%20*%2F%0Aclass%20RadioTaxonomy%20implements%20HooksInterface%20%7B%0A%0A%20%20%20%20public%20function%20__construct(%24radioTaxonomyFactory%2C%20%24taxonomies%20%3D%20array())%7B%0A%0A%20%20%20%20%20%20%20%20%24this-%3EradioTaxonomyFactory%20%3D%20apply_filters(‘td_radio_taxonomy_factory’%2C%20%24radioTaxonomyFactory)%3B%0A%20%20%20%20%20%20%20%20%24this-%3Etaxonomies%20%20%20%20%20%20%20%20%20%20%20%3D%20apply_filters(‘td_radio_taxonomies’%2C%20%24taxonomies)%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20%40see%20Todolist%5CModels%5CHooksInterface%0A%20%20%20%20%20*%2F%0A%20%20%20%20public%20function%20hooks()%7B%0A%20%20%20%20%20%20%20%20add_action(%20’registered_taxonomy’%2C%20array(%20%24this%2C%20’loadRadioTaxonomy’%20)%20)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20function%20getTaxonomies()%7B%0A%20%20%20%20%20%20%20%20return%20%24this-%3Etaxonomies%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20function%20loadRadioTaxonomy(%24taxonomy)%7B%0A%20%20%20%20%20%20%20%20if(!in_array(%24taxonomy%2C%20%24this-%3EgetTaxonomies()))%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%24this-%3EradioTaxonomyFactory-%3EcreateRadioButton(%24taxonomy)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%0A%7D%0A” message=”” highlight=”17,35″ provider=”manual”/]
On constate encore une fois que ce service implémente HooksInterface. C’est un service qui s’active lors de l’exécution du plugin.
- Ligne 17 : Le constructeur contient deux paramètres :
- $radioTaxonomyFactory : On donne au service la factory sur laquelle il peut travailler. On ajoute également un filtre WordPress pour que quelqu’un puisse la changer sans pour autant changer le code du plugin.
- $taxonomies : un tableau de taxonomy. Il contient les taxonomies pour lesquelles nous souhaitons modifier les checkbox en bouton radio. Il y a également un filtre pour que l’on puisse venir ajouter d’autres taxonomies que celles du plugin. (* voir à la fin du tutoriel une note sur cette possibilité)
- Ligne 35 : Cette fonction intervient sur le hook registered_taxonomy. Elle vérifie que la taxonomy en cours d’enregistrement existe dans notre tableau de taxonomies à modifier. Si jamais il y a besoin de modifier notre taxonomy, on fait appel à la factory !
Pour instancier ce service, voici notre fichier wp-todolist.php avec toutes les modifications que l’on va lui apporter :
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F**%0A%20*%20Plugin%20Name%3A%20WP%20Todolist%0A%20*%20Description%3A%20Gestion%20d’une%20Todolist%20dans%20WordPress%0A%20*%20Version%3A%201.0.0%0A%20*%20Author%3A%20%3Ca%20href%3D%22http%3A%2F%2Ftwitter.com%2FTDeneulin%22%20target%3D%22_blank%22%3EThomas%20DENEULIN%3C%2Fa%3E%0A%20*%2F%0A%0Arequire_once(%20dirname(%20__FILE__%20)%20.%20’%2Fvendor%2Fautoload.php’%20)%3B%0A%0Ause%20Todolist%5CTodolist%3B%0A%0Ause%20Todolist%5CWordPress%5CHelpers%5CTaxonomy%3B%0A%0Ause%20Todolist%5CWordPress%5CPostType%5CTodo%3B%0A%0Ause%20Todolist%5CWordPress%5CTaxonomy%5CState%3B%0A%0Ause%20Todolist%5CWordPress%5CServices%5CRadioTaxonomy%3B%0Ause%20Todolist%5CWordPress%5CServices%5CRadioTaxonomyFactory%3B%0A%0Adefine(%22TD_PLUGIN_PATH%22%2C%20plugin_dir_path(%20__FILE__%20))%3B%0Adefine(%22TD_PLUGIN_DIR_TEMPLATES%22%2C%20TD_PLUGIN_PATH%20.%20%22templates%22%20)%3B%0Adefine(%22TD_PLUGIN_DIR_TEMPLATES_ADMIN%22%2C%20TD_PLUGIN_DIR_TEMPLATES%20.%20%22%2Fadmin%22%20)%3B%0A%0A%24actions%20%3D%20array(%0A%20%20%20%20new%20Todo()%2C%0A%20%20%20%20new%20State()%2C%0A%20%20%20%20new%20RadioTaxonomy(%0A%20%20%20%20%20%20%20%20new%20RadioTaxonomyFactory()%2C%0A%20%20%20%20%20%20%20%20array(%0A%20%20%20%20%20%20%20%20%20%20%20%20Taxonomy%3A%3ASTATE%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A)%3B%0A%0A%0A%24todolist%20%3D%20new%20Todolist(%24actions)%3B%0A%24todolist-%3Eexecute()%3B” message=”” highlight=”23-25, 30-35″ provider=”manual”/]
- Ligne 23-25 : j’ai créé des constantes afin d’accéder aux chemins des fichiers dont nous allons avoir besoin, notamment celui qui correspond au template des boutons radios.
- Ligne 30-35 : je crée le service que l’on vient de voir au dessus en lui passant les paramètres nécessaires à sa construction : la RadioTaxonomyFactory et l’unique taxonomy dont on va se servir pour l’instant.
Place à notre factory :
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FServices%2FRadioTaxonomyFactory.php%0Anamespace%20Todolist%5CWordPress%5CServices%3B%0A%0Ause%20Todolist%5CException%5CTaxonomyObjectNotExist%3B%0Ause%20Todolist%5CException%5CInterfaceException%3B%0Ause%20Todolist%5CWordPress%5CHandler%5CRadioTaxonomyHandler%3B%0Ause%20Todolist%5CModels%5CRadioTaxonomyInterface%3B%0A%0A%2F**%0A%20*%20RadioTaxonomyFactory%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%20%3Ccontact%40wp-god.com%3E%0A%20*%20%40since%201.0.0%0A%20*%20%40version%201.0.0%0A%20*%2F%0Aclass%20RadioTaxonomyFactory%20%7B%0A%0A%0A%20%20%20%20public%20function%20createRadioButton(%24taxonomy)%7B%0A%20%20%20%20%20%20%20%20%24taxonomyObject%20%3D%20get_taxonomy(%20%24taxonomy%20)%3B%0A%0A%20%20%20%20%20%20%20%20if(%24taxonomyObject%20%3D%3D%3D%20false)%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20TaxonomyObjectNotExist(__(%22Taxonomy%20does%20not%20exist%22%2C%20%22td%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%24radioTaxonomyHandler%20%3D%20apply_filters(‘td_radio_taxonomy_handler’%2C%20new%20RadioTaxonomyHandler())%3B%0A%0A%20%20%20%20%20%20%20%20if(!%24radioTaxonomyHandler%20instanceOf%20RadioTaxonomyInterface)%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20InterfaceException(__(%22Handler%20not%20implements%20RadioTaxonomyInterface%22%2C%20%22td%22))%3B%20%20%20%20%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%24radioTaxonomyHandler-%3EsetTaxonomy(%24taxonomy)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%3EsetTaxonomyObject(%24taxonomyObject)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%3EcreateRadioButton()%3B%0A%0A%20%20%20%20%7D%0A%20%20%20%0A%7D%0A” message=”” highlight=”25,28,31,34-36″ provider=”manual”/]
- Ligne 25 : on retrouve ici l’une des deux exceptions personnalisées de notre plugin. Je n’en ai pas encore parlé car ce n’est pas le but recherché sur ce tutoriel. Je vous invite à aller voir le code source sur GitHub si cela vous intéresse.
- Ligne 28 : j’instancie le handler qui va nous permettre de faire les modifications que l’on souhaite. Encore un filtre WordPress si jamais vous souhaitez faire votre propre handler. Peut-être avez-vous une meilleur solution ou souhaitez-vous faire autrement ? Toujours est-il que pour que vous puissiez passer dans la factory, votre handler devra implémenter la RadioTaxonomyInterface ! Elle contient 5 méthodes : 2 setters / 2 getters et la méthode createRadioButton.
- Ligne 31 : une autre exception personnalisée.
- Ligne 34-36 : je fais de l’injection de dépendance grâce aux setters de mon handler. Je lui donne la taxonomy sur laquelle il doit travailler ainsi que le résultat de la fonction get_taxonomy. Puis, il ne lui reste plus qu’à créer le bouton !
Pour le handler, je ne vais pas vous mettre tout le code mais uniquement les parties que je souhaite développer puisqu’il est disponible sur GitHub et qu’il est plus important que les autres.
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FHandler%2FRadioTaxonomyHandler.php%0Anamespace%20Todolist%5CWordPress%5CHandler%3B%0A%0Ause%20Todolist%5CModels%5CRadioTaxonomyInterface%3B%0Ause%20Todolist%5CWordPress%5CWalker%5CRadioTaxonomyWalker%3B%0A%0A%2F**%0A%20*%20RadioTaxonomyHandler%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%20%3Ccontact%40wp-god.com%3E%0A%20*%20%40since%201.0.0%0A%20*%20%40version%201.0.0%0A%20*%2F%0Aclass%20RadioTaxonomyHandler%20implements%20RadioTaxonomyInterface%20%7B%0A%0A%20%20%20%20%2F%2F%20….%0A%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20%40see%20Todolist%5CModels%5CRadioTaxonomyInterface%0A%20%20%20%20%20*%2F%0A%20%20%20%20public%20function%20createRadioButton()%7B%0A%0A%20%20%20%20%20%20%20%20add_action(%20’admin_menu’%2C%20array(%20%24this%2C%20’removeMetaBox’%20)%20)%3B%0A%20%20%20%20%20%20%20%20add_action(%20’add_meta_boxes’%2C%20array(%20%24this%2C%20’addMetaBox’%20)%20)%3B%0A%20%20%20%20%20%20%20%20add_filter(%20’wp_terms_checklist_args’%2C%20array(%20%24this%2C%20’filterChecklistArgs’%20)%20)%3B%0A%0A%20%20%20%20%20%20%20%20add_action(%20’save_post’%2C%20array(%20%24this%2C%20’saveOneTerm’%20)%20)%3B%0A%20%20%20%20%20%20%20%20add_action(%20’edit_attachment’%2C%20array(%20%24this%2C%20’saveOneTerm’%20)%20)%3B%0A%0A%20%20%20%20%20%20%20%20add_action(%20’load-edit.php’%2C%20array(%20%24this%2C%20’hierarchicalTaxonomy’%20)%20)%3B%0A%20%20%20%20%20%20%20%20add_action(%20’quick_edit_custom_box’%2C%20array(%20%24this%2C%20’quickEditCustomNonce’%20)%20)%3B%20%20%0A%20%20%20%20%7D%0A%0A%0A%0A%20%20%20%20public%20function%20addMetaBox()%20%7B%0A%20%20%20%20%20%20%20%20foreach%20(%20%24this-%3EgetTaxonomyObject()-%3Eobject_type%20as%20%24postType%20)%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24label%20%3D%20%24this-%3EgetTaxonomyObject()-%3Elabels-%3Esingular_name%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24id%20%20%20%20%3D%20(!is_taxonomy_hierarchical(%20%24this-%3EgetTaxonomy()%20))%20%3F%20’radio-tagsdiv-‘%20.%20%24this-%3EgetTaxonomy()%20%3A%20’radio-‘%20.%20%24this-%3EgetTaxonomy()%20.%20’div’%20%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20add_meta_box(%20%24id%2C%20%24label%20%2Carray(%20%24this%2C’radioMetabox’%20)%2C%20%24post_type%20%2C%20’side’%2C%20’core’%2C%20array(%20’taxonomy’%3D%3E%24this-%3EgetTaxonomy()%20)%20)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%0A%20%20%20%20public%20function%20radioMetabox(%20%24post%2C%20%24metabox%20)%20%7B%0A%20%20%20%20%20%20%20%20%24defaults%20%3D%20array(%0A%20%20%20%20%20%20%20%20%20%20%20%20’taxonomy’%20%3D%3E%20’category’%0A%20%20%20%20%20%20%20%20)%3B%0A%20%20%20%20%20%20%20%20%24args%20%20%20%20%20%3D%20array()%3B%0A%20%20%20%20%20%20%20%20if%20(isset(%24metabox%5B’args’%5D)%20%7C%7C%20is_array(%24metabox%5B’args’%5D)%20)%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24args%20%3D%20%24metabox%5B’args’%5D%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%24args%20%20%20%20%20%20%20%20%20%20%20%3D%20wp_parse_args(%24args%2C%20%24defaults)%3B%0A%20%20%20%20%20%20%20%20%24checked_terms%20%20%3D%20%24post-%3EID%20%3F%20get_the_terms(%20%24post-%3EID%2C%20%24args%5B%22taxonomy%22%5D%20)%20%3A%20array()%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%24single_term%20%20%20%20%3D%20!%20empty(%20%24checked_terms%20)%20%26%26%20!%20is_wp_error(%20%24checked_terms%20)%20%3F%20array_pop(%20%24checked_terms%20)%20%3A%20false%3B%0A%20%20%20%20%20%20%20%20%24single_term_id%20%3D%20%24single_term%20%3F%20(int)%20%24single_term-%3Eterm_id%20%3A%200%3B%0A%0A%20%20%20%20%20%20%20%20include_once(apply_filters(%22td_radio_taxonomy_metabox%22%2C%20TD_PLUGIN_DIR_TEMPLATES_ADMIN%20.%20%22%2Fradio-taxonomy-metabox.php%22))%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20function%20filterChecklistArgs(%24args)%7B%0A%0A%20%20%20%20%20%20%20%20if(%20!array_key_exists(%22taxonomy%22%2C%20%24args)%20%7C%7C%20%24this-%3EgetTaxonomy()%20!%3D%20%24args%5B’taxonomy’%5D%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%24args%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%24args%5B’walker’%5D%20%20%20%20%20%20%20%20%3D%20apply_filters(‘td_radio_taxonomy_walker’%2C%20new%20RadioTaxonomyWalker())%3B%0A%20%20%20%20%20%20%20%20%24args%5B’checked_ontop’%5D%20%3D%20false%3B%0A%0A%20%20%20%20%20%20%20%20return%20%24args%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20….%0A%20%20%20%0A%7D%0A” message=”” highlight=”63, 72″ provider=”manual”/]
Vous avez donc la liste de tous les hooks dont on a besoin. Je supprime la metabox qui servait aux checkbox pour créer notre propre metabox. Ensuite, lorsque ma nouvelle metabox va lister les différents terms de la taxonomy grâce à la fonction wp_terms_checklist je vais lui associer un Walker spécifique : celui qui va permettre d’afficher des boutons radios. Cette fonction peut déjà prendre en paramètre un walker mais j’ai préféré le hooker sur wp_terms_checklist_args.
Vous constaterez que je n’ai pas écris d’HTML dans ce fichier. Or, il faudra bien le faire au niveau du callback de la fonction add_meta_box (radioMetabox). En effet, je trouve ça illisible de voir du PHP et du HTML se mélanger à ce niveau là alors qu’il y a d’autres solutions comme on peut le voir à la ligne 63. Je vais inclure le fichier HTML qui va contenir tout ce que je souhaite. Il peut vous arriver de devoir stocker du HTML dans une variable. Pour cela, il vous suffit de jouer avec les fonctions ob_start / ob_flush / ob_end
Concernant ce fichier, je n’ai pas réinventé la roue et je suis aller voir les templates de WordPress. J’adapte ensuite en fonction de mes besoins.
- Ligne 72 : on retrouve ici le Walker que j’instancie afin de filtrer ma fonction wp_terms_checklist uniquement sur les taxonomies qui en ont besoin. Il y a de nouveau un filtre si jamais vous voulez votre propre Walker !
En ce qui concerne le Walker, celui-ci hérite du Walker_Category de WordPress. J’ai uniquement besoin de surcharger la méthode start_el afin d’afficher les boutons radios que je souhaite. Ca peut paraître lourd pour modifier le type radio sur l’input… mais WordPress n’a toujours pas de Walker pour les afficher comme des boutons radios. C’est quelque chose que l’on pourrait proposer dans un ticket cette affaire… !
[pastacode lang=”php” manual=”%3C%3Fphp%0A%0A%2F%2F%20wp-todolist%2Fsrc%2FTodolist%2FWordPress%2FWalker%2FRadioTaxonomyWalker.php%0Anamespace%20Todolist%5CWordPress%5CWalker%3B%0A%0A%0A%2F**%0A%20*%20RadioTaxonomyWalker%0A%20*%0A%20*%20%40author%20Thomas%20DENEULIN%20%3Ccontact%40wp-god.com%3E%0A%20*%20%40since%201.0.0%0A%20*%20%40version%201.0.0%0A%20*%2F%0Aclass%20RadioTaxonomyWalker%20extends%20%5CWalker_Category%20%7B%0A%0A%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20Start%20the%20element%20output.%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40see%20Walker%3A%3Astart_el()%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40since%202.5.1%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40param%20string%20%24output%20%20%20Passed%20by%20reference.%20Used%20to%20append%20additional%20content.%0A%20%20%20%20%20*%20%40param%20object%20%24category%20The%20current%20term%20object.%0A%20%20%20%20%20*%20%40param%20int%20%20%20%20%24depth%20%20%20%20Depth%20of%20the%20term%20in%20reference%20to%20parents.%20Default%200.%0A%20%20%20%20%20*%20%40param%20array%20%20%24args%20%20%20%20%20An%20array%20of%20arguments.%20%40see%20wp_terms_checklist()%0A%20%20%20%20%20*%20%40param%20int%20%20%20%20%24id%20%20%20%20%20%20%20ID%20of%20the%20current%20term.%0A%20%20%20%20%20*%2F%0A%20%20%20%20public%20function%20start_el(%20%26%24output%2C%20%24category%2C%20%24depth%20%3D%200%2C%20%24args%20%3D%20array()%2C%20%24id%20%3D%200%20)%20%7B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%24taxonomy%20%3D%20’category’%3B%0A%20%20%20%20%20%20%20%20if%20(%20!empty(%20%24args%5B’taxonomy’%5D%20)%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24taxonomy%20%3D%20%24args%5B’taxonomy’%5D%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%0A%20%20%20%20%20%20%20%20%24name%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20’radio_tax_input%5B’.%24taxonomy.’%5D’%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%24args%5B’popular_cats’%5D%20%20%3D%20empty(%20%24args%5B’popular_cats’%5D%20)%20%3F%20array()%20%3A%20%24args%5B’popular_cats’%5D%3B%0A%20%20%20%20%20%20%20%20%24class%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20in_array(%20%24category-%3Eterm_id%2C%20%24args%5B’popular_cats’%5D%20)%20%3F%20’%20class%3D%22popular-category%22’%20%3A%20”%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%24args%5B’selected_cats’%5D%20%3D%20empty(%20%24args%5B’selected_cats’%5D%20)%20%3F%20array()%20%3A%20%24args%5B’selected_cats’%5D%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%24selected_term%20%20%20%20%20%20%20%20%20%3D%20!empty(%20%24args%5B’selected_cats’%5D%20)%20%26%26%20!%20is_wp_error(%20%24args%5B’selected_cats’%5D%20)%20%3F%20array_pop(%20%24args%5B’selected_cats’%5D%20)%20%3A%20false%3B%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%24selected_id%20%20%20%20%20%20%20%20%20%20%20%3D%20(%20%24selected_term%20)%20%3F%20%24selected_term%20%3A%200%3B%0A%0A%20%20%20%20%20%20%20%20if%20(%20!%20empty(%20%24args%5B’list_only’%5D%20)%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24aria_cheched%20%3D%20’false’%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24inner_class%20%3D%20’category’%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(%20in_array(%20%24category-%3Eterm_id%2C%20%24args%5B’selected_cats’%5D%20)%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24inner_class%20.%3D%20’%20selected’%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24aria_cheched%20%3D%20’true’%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%24output%20.%3D%20%22%5Cn%22%20.%20’%3Cli’%20.%20%24class%20.%20’%3E’%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20’%3Cdiv%20class%3D%22’%20.%20%24inner_class%20.%20’%22%20data-term-id%3D’%20.%20%24category-%3Eterm_id%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20’%20tabindex%3D%220%22%20role%3D%22radio%22%20aria-checked%3D%22’%20.%20%24aria_cheched%20.%20’%22%3E’%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20esc_html(%20apply_filters(%20’the_category’%2C%20%24category-%3Ename%20)%20)%20.%20’%3C%2Fdiv%3E’%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%24output%20.%3D%20%22%5Cn%3Cli%20id%3D’%7B%24taxonomy%7D-%7B%24category-%3Eterm_id%7D’%24class%3E%22%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20’%3Clabel%20class%3D%22selectit%22%3E%3Cinput%20value%3D%22’%20.%20%24category-%3Eterm_id%20.%20’%22%20type%3D%22radio%22%20name%3D%22′.%24name.’%5B%5D%22%20id%3D%22in-‘.%24taxonomy.’-‘%20.%20%24category-%3Eterm_id%20.%20’%22’%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20checked(%20%24category-%3Eterm_id%2C%20%24selected_id%2C%20false%20)%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20disabled(%20empty(%20%24args%5B’disabled’%5D%20)%2C%20false%2C%20false%20)%20.%20’%20%2F%3E%20’%20.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20esc_html(%20apply_filters(%20’the_category’%2C%20%24category-%3Ename%20)%20)%20.%20’%3C%2Flabel%3E’%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20Ends%20the%20element%20output%2C%20if%20needed.%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40since%202.1.0%0A%20%20%20%20%20*%20%40access%20public%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40see%20Walker%3A%3Aend_el()%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40param%20string%20%24output%20Passed%20by%20reference.%20Used%20to%20append%20additional%20content.%0A%20%20%20%20%20*%20%40param%20object%20%24page%20%20%20Not%20used.%0A%20%20%20%20%20*%20%40param%20int%20%20%20%20%24depth%20%20Optional.%20Depth%20of%20category.%20Not%20used.%0A%20%20%20%20%20*%20%40param%20array%20%20%24args%20%20%20Optional.%20An%20array%20of%20arguments.%20Only%20uses%20’list’%20for%20whether%20should%20append%0A%20%20%20%20%20*%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20to%20output.%20See%20wp_list_categories().%20Default%20empty%20array.%0A%20%20%20%20%20*%2F%0A%20%20%20%20public%20function%20end_el(%20%26%24output%2C%20%24page%2C%20%24depth%20%3D%200%2C%20%24args%20%3D%20array()%20)%20%7B%0A%0A%20%20%20%20%20%20%20%20%24output%20.%3D%20%22%3C%2Fli%3E%5Cn%22%3B%0A%20%20%20%20%7D%0A%0A%0A%0A%7D%0A” message=”” highlight=”” provider=”manual”/]
Nous voilà avec des boutons radios pour notre plugin !
Vous pouvez retrouver tout le code source de cette partie du tutoriel dans la branch part-2 sur Github.
* Utiliser une fonction d’un plugin pour ses propres besoins
Je dédie cette petite étoile, faîte précédemment, car notre fonctionnalité, qui permet de créer des boutons radio au lieu de checkbox, peut être utilisée par tous.
C’est à dire que n’importe quel thème ou n’importe quel plugin pourrait décider de venir se hooker sur notre tableau de taxonomies pour créer / changer l’apparence de ces taxonomies au niveau des metabox d’une checkbox à un bouton radio.
On est tous d’accord pour dire que ce ne serait pas une bonne pratique puisque :
- Ce n’est pas le but de notre plugin. A la base, ce hook pourrait permettre à une personne de faire chemin inverse. Elle pourrait vouloir qu’une todo ait plusieurs State en même temps.
- On ne va pas faire installer ce plugin uniquement pour récupérer cette fonctionnalité.
Que faire ?
En ayant codé de cette manière, on ne peut que constater que ce genre de code doit :
- Soit appartenir au core de WordPress. Cela demande donc un ticket, du temps et de défendre son bifteck. Puis de manière générale, ce ne serait pas implémenté de cette manière là dans le core.
- Soit se trouver dans une librairie
En effet, nous pourrions isoler ce code en dehors de notre plugin et le mettre à part sous la forme d’une librairie. De fait, on pourrait l’implémenter avec composer pour charger cette fonctionnalité externe à notre plugin. Il ne suffit que d’une ligne de commande et d’en faire un bon usage. Le fait d’embarquer ensuite ce vendor n’est en rien contraignant puisqu’il n’est dépendant de personne si ce n’est que de lui-même ! (et de fonctionner uniquement sur WordPress forcément).
0 Commentaire