Une extension pour ajouter des fonctionnalités au thème Divi (WordPress)

Une extension pour ajouter des fonctionnalités au thème Divi (WordPress)

Le thème Divi est très bien, mais il lui manque quelques fonctionnalités pratiques. J’ai créé précédemment un thème enfant pour Divi afin de générer différemment les pages de catégorie. Mais pour ajouter d’autres fonctionnalités, il était préférable de créer une extension, afin de ne pas mélanger les choses. Je commente ici l’extension créée.

Ce que je voulais obtenir

  • le style des méta-données de catégorie dans les pages de catégorie doivent être de la couleur des liens du site. Plutôt que de modifier à la main la feuille de style du thème enfant, je voulais trouver automatiquement la couleur définie dans Divi pour les liens et l’appliquer à ces méta-données par  le biais d’un style défini en code (inline style).
  • Lorsqu’on écrit un nouvel article, ou n’importe quel autre type de contenu, avec l’éditeur classique, on dispose d’un moyen de changer la couleur du texte. Mais les couleurs proposées ne sont pas celles du site. Je voulais donc trouver automatiquement la palette par défaut du site (définie dans les options Divi, onglet général) et utiliser cette palette comme couleurs possibles pour le texte.
  • Je voulais aussi supprimer la possibilité d’insérer manuellement un titre H1 dans un contenu. Les titres H1 sont réservés aux titres de pages web et sont générés automatiquement par le thème. Il ne doit pas y avoir de deuxième titre H1 dans une page car ça « perturbe » les moteurs de recherche.

L’extension finie

L’extension (sur GitHub, ou la version 1.0 – zip – commentée ici, en téléchargement : Extension « Divi Add functions » (zip) ) peut être installée sur un site WordPress par le biais d’ajout d’extension. Il suffit ensuite de l’activer, il n’y a pas de réglage à réaliser.

Je suis partie d’une extension réalisée par Divi Space, ici sur GitHub et je l’ai modifiée de manière extensive.

Trouver la couleur appliquée aux liens

Dans le répertoire /wp-content/plugins/clea-divi-add-functions/includes , deux fichiers (cdaf-enqueues.php  et cdaf-editor.php ) contiennent les lignes :

$accent_color = esc_html( et_get_option( 'accent_color', '#2ea3f2' ) );
$link_color = et_get_option( 'link_color', $accent_color );

$link_color prend la valeur définie dans le paramètre « COULEUR DU LIEN DU CORPS » de la personnalisation du thème Divi (ou du thème enfant), onglet « Paramètres Généraux » >> « Typographie ». Je ne suis pas certaine de ce à quoi correspond accent_color…

Définir un style en ligne (inline style)

C’est en regardant le code de l’extension réalisée par Divi Space, ici sur GitHub que j’ai compris comment on fait ça. Le fichier contient :

add_action( 'wp_enqueue_scripts', 'cdaf_divi_enqueue_scripts' );

function cdaf_divi_enqueue_scripts() {
	
	if ( wp_basename( get_bloginfo( 'template_directory' ) ) == 'Divi' ) {
		wp_enqueue_style( 'cdaf-stylesheet', CDAF_DIR_URL . 'css/clea-divi-add-functions.css' );
		
		$accent_color = esc_html( et_get_option( 'accent_color', '#2ea3f2' ) );
		$link_color = et_get_option( 'link_color', $accent_color );
		
		// there must be an extra } at the end of the custom_css string...
		$custom_css = "#left-area .post-meta a[rel='category tag'] {color: {$link_color};}}";
		wp_add_inline_style( 'cdaf-stylesheet', $custom_css );	
	}
}

La fonction exécute les instructions suivantes :

  • si (et seulement si) le thème Divi (ou un thème enfant de Divi) est utilisé, alors :
    1. charge la feuille de style css/clea-divi-add-functions.css (qui ne contient rien actuellement) – cette instruction ne sert à rien en l’état actuel de l’extension.
    2. Trouve quel est le code de la couleur définie pour les liens
    3. ajoute un style « inline » qui contient le texte défini dans la variable $custom_css . Il faut bien noter qu’il y a deux } à la fin de ce texte.

Et maintenant, si je regarde dans le <head> d’une page du site (par exemple https://parcours-performance.com), je trouve :

<link rel='stylesheet' id='cdaf-stylesheet-css' href='https://parcours-performance.com/wp-content/plugins/clea-divi-add-functions/css/clea-divi-add-functions.css?ver=4.8.2' type='text/css' media='all'/>
<style id='cdaf-stylesheet-inline-css' type='text/css'>
#left-area .post-meta a[rel='category tag'] {color: #843bbb;}}
</style>

La feuille de style est chargée, et un style « inline » est défini pour #left-area .post-meta a[rel=’category tag’].

Trouver la palette de couleurs définie par l’utilisateur de Divi

Le fichier cdaf-utilities.php  contient la fonction suivante :

function cdaf_read_color_palette() {
	
	$cdaf_color_palette = et_get_option( 'divi_color_palette', false ) ; 
	$return = explode("|", $cdaf_color_palette);
	return $return ;
}

Cette fonction va chercher la valeur de ‘divi_color_palette’ et me renvoie une chaîne de caractères (string) avec les codes couleurs séparés par |. Avec explode, je place dans $return  un tableau (array) contenant les codes hexadécimaux des couleurs.

Modifier les couleurs proposées dans l’éditeur WordPress

L'éditeur WordPress : les couleurs de texte par défautA l’origine l’éditeur WordPress par défaut propose un ensemble de couleurs, plus des cases « personnalisées » que l’on ne peut pas modifier.

Le fichier cdaf-editor.php  contient le code correspondant à cette fonctionnalité. Dedans, deux fonctions assurent la modification des couleurs proposées par l’éditeur WordPress.

  • La fonction cdaf_editor_colors( $init ) assure le passage de l’état initial (image 1) à l’état 2 (image 2) dans lequel les couleurs par défaut sont remplacées par les couleurs du site.
  • La fonction cdaf_tiny_mce_remove_custom_colors( $plugins ) s’occupe d’enlever les cases blanches « personnalisée » qui ne fonctionnent pas. Je l’ai trouvée sur StackExchange. Je n’ai pas cherché à comprendre comment elle marche.

Intéressons nous à la fonction  cdaf_editor_colors( $init ) , que j’ai eu bien du mal à créer, même si je suis partie d’une solution initiale qui fonctionnait (disponible ici). Elle contient les éléments suivants (que j’ai divisé en quatre parties) :

function cdaf_editor_colors($init) {

	/*********** PARTIE 1 **************************
	***********************************************/
	$cdaf_colors = cdaf_read_color_palette() ; // an array of hex codes beginning with #
	$index = 0 ;
	$default_colors =  array();
	
	// what is the link color for text ?
	$accent_color = esc_html( et_get_option( 'accent_color', '#2ea3f2' ) );
	$link_color = et_get_option( 'link_color', $accent_color );
	// remove # in color code
	$link_color = ltrim( $link_color, "#" ) ;
	// met en majuscule le code HEX
	$link_color = strtoupper( $link_color );		

	/*********** PARTIE 2 **************************
	***********************************************/
	// transformer en array avec code hex sans # et nom sous forme "color n"
	foreach ( $cdaf_colors as $color ) {

		// remove # in color code
		$color = ltrim( $color, "#" ) ;
		// met en majuscule le code HEX
		$color = strtoupper( $color );	

		if ( !( $link_color == $color ) ) {

			// the text color palette should not include the link color
			$default_colors[] = array( $color, 'color ' . $index  )  ;
			
			$index++ ; 			
		
		}
					
	}

	// the only way I found to have a string which works....
	// the string shoulr be like this : '"423432","color 0","FFFFFF","color 1","4C858D","color 2","ED693B","color 3","F28A2B","color 4","FAC079","color 5","F6DB6B","color 6","A60F65","color 7"' 
	$custom_colours = wp_json_encode( $default_colors ) ;
	$replace = array( "[", "]") ; // we don't want these in the final string
	$custom_colours = str_replace($replace, "", $custom_colours );

	/*********** PARTIE 3 **************************
	***********************************************/
    // build colour grid default+custom colors
    $init['textcolor_map'] = '['.$custom_colours.']';

    // change the number of rows in the grid if the number of colors changes
    // 8 swatches per row
    $init['textcolor_rows'] = 1;

	// debug will echo in the footer of the editor ! 
	//echo "<p>" . $custom_colours . "</p>" ;

    return $init;
}
	/*********** PARTIE 4 **************************
	***********************************************/
add_filter('tiny_mce_before_init', 'cdaf_editor_colors');

Partie 1 : Définir les variables qui vont nous intéresser

Ces variables sont la palette par défaut (celle que l’administrateur a défini dans les options Divi) ainsi que la couleur des liens définie dans la personnalisation du thème. Avec la palette par défaut, j’ai la liste des codes couleur que je voudrais intégrer dans l’éditeur WordPress. Mais on ne veut pas que la couleur des liens soit accessible à un auteur de contenu. L’internaute ne comprendrait pas que certains textes d’une couleur soient cliquables quand d’autres ne le sont pas. Il faut donc que je sache quelle est cette couleur de lien.

J’obtiens deux variables qui contiennent les données définies par l’administrateur du site :

  • $link_color , qui contient un code à 6 chiffres ou lettres A à F en majuscules. Le # du code a été enlevé.
  • $default_colors , un tableau (array) tel que défini précédemment.

Partie 2 : transformer l’array de couleurs

Je dois obtenir une chaîne de caractère au format suivant :

'"423432","color 0","FFFFFF","color 1","4C858D","color 2","ED693B","color 3","F28A2B","color 4","FAC079","color 5","F6DB6B","color 6","A60F65","color 7"'

Je pars d’un tableau (array) qui contient simplement les codes Hexadécimaux des couleurs sous la forme #AABBCC. Pour aboutir à la chaîne de caractère ci-dessus, il faut :

  • lire chaque élément du tableau (array), avec foreach ( $cdaf_colors as $color )
  • pour chaque valeur ($color) :
    • enlèver le # devant (ltrim( $color, « # » ) ) et le mettre en majuscule (strtoupper( $color ) ) ;
    • si elle est égale à la couleur utilisée pour les liens je ne l’utilise pas ;
    • sinon, je la place dans un nouveau tableau, sous la forme (code Hex, nom couleur).

A la sortie de cette boucle, $default_colors contient toutes les couleurs à utiliser, avec leur nom (sous la forme « couleur n »). Mais ce n’est toujours pas une chaîne de caractères…

J’ai passé beaucoup, beaucoup de temps à bloquer là dessus. Tout le monde saisit à la main les couleurs à intégrer dans l’éditeur, sans profiter de l’automatisation possible. Mais j’ai trouvé !!!

  • avec wp_json_encode( $default_colors ) , on transforme le tableau (array) en une chaîne de caractères ;
  • avec str_replace($replace, «  », $custom_colours ) , on enlève tous les caractères dont on ne veut pas, définis dans $replace = array( « [« , « ] ») .

Partie 3 indiquer à l’éditeur les nouvelles couleurs de texte

Là je me suis bornée à recopier ce qui était écrit dans le code proposé sur StackExchange.

La seule difficulté (importante) était de fournir une chaîne de caractères au bon format.

Partie 4 dire à WordPress de charger la fonction

Avec add_filter(‘tiny_mce_before_init’, ‘cdaf_editor_colors’); , on dit qu’au moment où l’éditeur s’ouvre, il doit exécuter la fonction cdaf_editor_colors.

A ce stade, lorsqu’on utilise l’éditeur, il nous propose les couleurs définies dans les options de Divi, moins celle qui est dédiée aux liens :

L'éditeur WordPress : des couleurs de texte personnalisées V1

Je n’aime pas du tout les carrés « personnalisée » en dessous. Je n’y faisais pas attention avant, mais là, ça me gène. Alors j’ai ajouté une autre fonction, trouvée dans une autre discussion sur StackExchange :

add_filter( 'tiny_mce_plugins', 'cdaf_tiny_mce_remove_custom_colors' );

function cdaf_tiny_mce_remove_custom_colors( $plugins ) {       

    foreach ( $plugins as $key => $plugin_name ) {
        if ( 'colorpicker' === $plugin_name ) {
            unset( $plugins[ $key ] );
            return $plugins;            
        }
    }

    return $plugins;            
}

Et maintenant, les choix de couleur de texte sont présentés de manière « propre » !

L'éditeur WordPress : des couleurs de texte personnalisées V2

Supprimer les titres H1 des styles proposés par l’éditeur WordPress

Je me suis bornée à reprendre le code proposé dans ce gist, sur GitHub.

function cdaf_remove_h1_from_editor( $settings ) {

    $settings['block_formats'] = 'Paragraph=p;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre;';
    return $settings;
}

add_filter('tiny_mce_before_init', 'cdaf_remove_h1_from_editor');

Avant, l’éditeur proposait aux auteurs des style Titre 1, maintenant il n’est plus possible de commettre l’erreur de donner un tel style à du texte.

L'éditeur WordPress : les styles par défaut   L'éditeur WordPress : pas de H1 dans les styles

Et maintenant ?

Si vous voulez télécharger l’extension complète : Extension « Divi Add functions » (zip). Je la ferai certainement évoluer ultérieurement. Mais pour l’instant elle répond à mes besoins.

Plesk, un site avec des fichiers html ou php, sans CMS

Plesk, un site avec des fichiers html ou php, sans CMS

J’hébergeais sur OVH un site assez simple, qui suit le bon fonctionnement de mes Raspberry Pi. Comme j’ai migré l’hébergement OVH sur un VPS OVH avec Plesk, j’ai également dû déplacer ce site.

J’explique donc ici comment installer un site composé de fichiers php et css dans un hébergement Plesk. Ce site n’a pas de base de données et n’utilise pas de gestionnaire de contenus type WordPress.

Créer un sous-domaine et y transférer les fichiers

J’ai ajouté un sous-domaine dans mon compte Plesk, par exemple domicile.mon-domaine.com.

J’y ai transféré les fichiers de l’hébergement initial en ftp. J’ai placé les fichiers dans un répertoire temporaire ‘temp-al’.

Placer les fichiers au bon endroit

Dans Plesk, tel qu’il est paramétré, les fichiers doivent être rangés comme suit :

  • css dans le répertoire domicile.mon-domaine.com/css
  • php ou html directement dans domicile.mon-domaine.com/
  • favicon.ico à la racine (image 256x256px)
NOTA : mon VPS Plesk étant sous linux, les fichiers doivent tous être avec des fins de ligne en mode linux, et pas windows. Sinon ça ne fonctionne pas. 

Donner l’accès au site

Lorsque le sous-domaine a été créé, Plesk y a placé un fichier index.html. Pour que ce soit index.php qui soit exécuté, il suffit de supprimer le fichier index.html.

Et maintenant, dans un navigateur, http://domicile.mon-domaine.com ouvre bien sur un site défini par le contenu de index.php.

Afficher les erreurs

En tant qu’administrateur, on peut modifier les paramètres PHP du sous-domaine et régler « display_error » sur « on ».

Et voilà !

Créer une page de réglage pour une extension WordPress

Créer une page de réglage pour une extension WordPress

Je poursuis le travail sur la création d’une extension « best practice » généré dans l’article précédent (Créer une extension WordPress ; Partie 1) de cette série. Une extension sur laquelle le webmaster ne peut pas intervenir n’est pas très intéressante puisqu’elle oblige à modifier le code à chaque fois qu’un paramètre change. J’explore donc ici la manière de créer une page de réglage pour l’extension. J’utilise l’API « settings » de WordPress car c’est une méthode plus sûre (la « sanitation » des données est assurée par WordPress).

Mes sources d’information

L’extension au départ

L’extension est au stade où je l’ai laissée dans le premier article de cette série :

Premier essai

J’ai fait plusieurs essais avant celui-ci mais à chaque fois j’avais un « petit » problème dont je n’arrivais pas trouver la solution. Je préfère donc repartir de zéro pour comprendre. J’ai tout simplement copié le contenu du « gist » de Anna et je l’ai placé dans un fichier « clea-add-button-settings-page.php  » du répertoire /admin. Dans le fichier principal de l’extension (clea-add-button.php), j’ai ajouté la ligne require_once CLEA_ADD_BUTTON_DIR_PATH . ‘admin/clea-add-button-settings-page.php’;  et j’ai rechargé la fenêtre de mon navigateur.

Une page de réglage « My Plugin Options » apparaît sous « réglages » (la copie d’écran correspond à ce qui est affiché une fois que j’ai enregistré une première fois des données) :

Essai 1 de page de réglage d'une extension

Essai 1 de page de réglage d’une extension

Et dans la base de données, la table « prefix-options » contient une nouvelle ligne (option_name = my-plugin-settings), qui contient :

a:4:{s:9:"field_1_1";s:4:"zzzz";s:9:"field_1_2";s:8:"ttttyyyy";s:9:"field_2_1";s:5:"hhhhh";s:9:"field_2_2";s:6:"mmmmmm";}

C’est donc exactement ce que je cherche à faire. Reste maintenant à le faire mieux !

Qu’est-ce que je veux faire ?

Je veux obtenir une page de réglage avec plusieurs sections et des champs qui pourraient être présentés sous forme de texte court, de texte long (textarea), de cases à cocher (radio ou checkbox), de liste déroulante, d’un éditeur wysiwig pour des textes sophistiqués et d’un sélecteur de couleur (color picker).

Je voudrais aussi pouvoir définir les sections et les champs dans des « arrays » (tableaux) et générer automatiquement la page de réglages.

Enfin, je voudrais qu’une case à cocher permette d’afficher des éléments de débogage à la fin de la page de réglages si je le souhaite.

Etape 1 : générer automatiquement l’exemple ci-dessus

Je vais modifier le gist de Anna pour générer automatiquement sa page de réglages avec moins de code.

Respecter les bonnes pratiques :

La première chose que je fais, c’est de remplacer ‘textdomain’ par ‘clea-add-button’ et de mettre un préfixe  clea_add_button_ devant toutes les fonctions.

regrouper la définition des sections et champs, automatiser la génération

J’ai regroupé la définition des sections à la fin, dans la fonction clea_add_button_settings_sections_val() . Idem pour la définition des champs, dans la fonction clea_add_button_settings_sections_val() .

Ainsi la fonction clea_add_button_admin_init()  ne contient plus qu’une boucle « foreach », qui lit les arrays de sections ou champs à générer et les génère.

<?php
/**
 *
 * Créer une page de settings pour l'extension

 *
 * @link       	
 * @since      	0.2.0
 *
 * @package    clea-add-button
 * @subpackage clea-add-button/includes
 * Text Domain: clea-add-button
 */

//  Based on Anna's gist https://gist.github.com/annalinneajohansson/5290405
// http://codex.wordpress.org/Settings_API

/**********************************************************************

* to set the title of the setting page see -- clea_add_button_options_page()
* to set the sections see -- clea_add_button_settings_sections_val()
* to set the fields see -- clea_add_button_settings_fields_val()

**********************************************************************/

// create the settings page and it's menu
add_action( 'admin_menu', 'clea_add_button_admin_menu' );

// set the content of the admin page
add_action( 'admin_init', 'clea_add_button_admin_init' );


function clea_add_button_admin_menu() {
	
    add_options_page( 
		__('Options de Clea Add Button', 'clea-add-button' ),	// page title (H1)	
		__('Options', 'clea-add-button' ),						// menu title
		'manage_options', 										// required capability
		'my-plugin', 											// menu slug (unique ID)
		'clea_add_button_options_page' );						// callback function
}

function clea_add_button_admin_init() {
  
  	register_setting( 'my-settings-group', 'my-plugin-settings' );
	
	$set_sections = clea_add_button_settings_sections_val() ;
 
	// add_settings_section
	foreach( $set_sections as $section ) {
		
		add_settings_section( 
			$section[ 'section_name' ], 
			$section[ 'section_title' ] ,
			$section[ 'section_callbk' ], 
			$section[ 'menu_slug' ]
		);
		
	}	

	$set_fields = clea_add_button_settings_fields_val() ;
	
	// add the fields
	foreach ( $set_fields as $section_field ) {

		foreach( $section_field as $field ){

			add_settings_field( 
				$field['field_id'], 
				$field['label'], 
				$field['field_callbk'],  
				$field['menu_slug'], 
				$field['section_name'] 
			);
		}

	}	
}


/**********************************************************************

* The actual page

**********************************************************************/
function clea_add_button_options_page() {
?>
  <div class="wrap">
      <h2><?php _e('My Plugin Options', 'clea-add-button'); ?></h2>
      <form action="options.php" method="POST">
        <?php settings_fields('my-settings-group'); ?>
        <?php do_settings_sections('my-plugin'); ?>
        <?php submit_button(); ?>
      </form>
  </div>
<?php }


function section_1_callback() {
	_e( 'Some help text regarding Section One goes here.', 'clea-add-button' );
}
function section_2_callback() {
	_e( 'Some help text regarding Section Two goes here.', 'clea-add-button' );
}


function field_1_1_callback() {
	
	$settings = (array) get_option( 'my-plugin-settings' );
	$field = "field_1_1";
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";
}
function field_1_2_callback() {
	
	$settings = (array) get_option( 'my-plugin-settings' );
	$field = "field_1_2";
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";
}
function field_2_1_callback() {
	
	$settings = (array) get_option( 'my-plugin-settings' );
	$field = "field_2_1";
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";
}
function field_2_2_callback() {
	
	$settings = (array) get_option( 'my-plugin-settings' );
	$field = "field_2_2";
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";
}
/*
* INPUT VALIDATION:
* */
function clea_add_button_settings_validate_and_sanitize( $input ) {
	$settings = (array) get_option( 'my-plugin-settings' );
	
	if ( $some_condition == $input['field_1_1'] ) {
		$output['field_1_1'] = $input['field_1_1'];
	} else {
		add_settings_error( 'my-plugin-settings', 'invalid-field_1_1', 'You have entered an invalid value into Field One.' );
	}
	
	if ( $some_condition == $input['field_1_2'] ) {
		$output['field_1_2'] = $input['field_1_2'];
	} else {
		add_settings_error( 'my-plugin-settings', 'invalid-field_1_2', 'You have entered an invalid value into Field One.' );
	}
	
	// and so on for each field
	
	return $output;
}

/**********************************************************************

* THE SECTIONS

**********************************************************************/

function clea_add_button_settings_sections_val() {

	$sections = array(
		array(
			'section_name' 	=> 'section-1', 
			'section_title'	=>  __( 'Section One', 'clea-add-button' ), 
			'section_callbk'=> 'section_1_callback', 
			'menu_slug'		=> 'my-plugin' ,								
		),
		array(
			'section_name' 	=> 'section-2',
			'section_title'	=>  __( 'Section Two', 'clea-add-button' ),
			'section_callbk'=> 'section_2_callback' ,
			'menu_slug'		=> 'my-plugin'
			),
	);	
	
	return $sections ;
	
}

/**********************************************************************

* THE FIELDS

**********************************************************************/
function clea_add_button_settings_fields_val() {



	$section_1_fields = array (
		array(
			'field_id' 		=> 'field-1-1', 							
			'label'			=> __( 'Field One', 'clea-add-button' ), 	
			'field_callbk'	=> 'field_1_1_callback', 					
			'menu_slug'		=> 'my-plugin', 							
			'section_name'	=> 'section-1', 							
		),	
		array(
			'field_id' 		=> 'field-1-2',
			'label'			=> __( 'Field Two', 'clea-add-button' ),
			'field_callbk'	=> 'field_1_2_callback', 
			'menu_slug'		=> 'my-plugin', 
			'section_name'	=> 'section-1'
		),
	);
	
	$section_2_fields = array (
		array(
			'field_id' 		=> 'field-2-1', 
			'label'			=> __( 'Field One', 'clea-add-button' ), 
			'field_callbk'	=> 'field_2_1_callback', 
			'menu_slug'		=> 'my-plugin', 
			'section_name'	=> 'section-2'			
		),
		array(
			'field_id' 		=> 'field-2-2', 
			'label'			=> __( 'Field Two', 'clea-add-button' ), 
			'field_callbk'	=> 'field_2_2_callback', 
			'menu_slug'		=> 'my-plugin', 
			'section_name'	=> 'section-2'
		),
	);
	

	$section_fields = array(
		'section-1'	=> $section_1_fields,
		'section-2' => $section_2_fields
	) ;	

	
	
	return $section_fields ;
}

Ajouter d’autres arguments pour les champs

Il faut que j’ajoute des arguments à chaque champs :

  • ‘type’ : dans l’exemple dont je pars, tous les champs sont des textes courts. Comme je ne veux pas avoir autant de fonctions que de champs pour les afficher, il faut que le programme sache quel est le type du champs pour savoir comment le restituer.
  • ‘helper’ : un texte qui aidera l’utilisateur à comprendre comment faire son réglage.
  • ‘default’ : qui permettra de définir une valeur si l’utilisateur n’en définit pas.

Chaque champs doit pouvoir être défini par un array contenant les 5 éléments obligatoires et les trois nouveaux.

array(
'field_id' => 'field-1-1', 
'label' => __( 'Field One', 'clea-add-button' ), 
'field_callbk' => 'field_1_1_callback', 
'menu_slug' => 'my-plugin', 
'section_name' => 'section-1',
'type' => 'text',
'helper' => __( 'help 1', 'clea-presentation' ),
'default' => '' 
),

Je me suis inspirée de cet article de Smashing Magazine pour faire ça.

Dans la fonction qui génère les champs, j’ajoute une dernière valeur qui correspondra à $args, le 6ème élément de la fonction add_settings_field.

// add the fields
foreach ( $set_fields as $section_field ) {

	foreach( $section_field as $field ){

		add_settings_field( 
			$field['field_id'], 
			$field['label'], 
			$field['field_callbk'],  
			$field['menu_slug'], 
			$field['section_name'],
			$field
		);
	}

}	

Et dans la fonction field_1_1_callback() , j’ajoute $argument  comme élément reçu, ainsi que l’affichage du contenu de cette variable (c’est un array, j’utilise print_r)  :

function field_1_1_callback( $arguments ) {
	
	$settings = (array) get_option( 'my-plugin-settings' );
	$field = "field_1_1";
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";
	echo "<hr /><pre>";
	print_r( $arguments ) ;
	echo "</pre><hr />";	
}

Et ça fonctionne !

Page de réglage d'une extension WordPress : affichage des arguments de add_settings_field

Page de réglage d’une extension WordPress : affichage des arguments de add_settings_field

Regrouper les fonctions « callback » pour les sections

Il n’y a pas beaucoup de sections en général mais ce n’est pas la peine de créer autant de fonctions que de sections.

Je crée donc une fonction clea_add_button_settings_section_callback( $args )  (que je n’oublie pas de déclarer comme ‘section_callbk’ dans la définition des sections) :

/**********************************************************************

* Section callback

**********************************************************************/

function clea_add_button_settings_section_callback( $args  ) {
	
	$sect_descr = array(

		'section-1' 	=> __( 'Text regarding Section One goes here.', 'clea-presentation' ),
		'section-2' 	=> __( 'Text regarding Section Two goes here.', 'clea-presentation' )
	);		

	$description = $sect_descr[ $args['id'] ] ;
	printf( '<span class="section-description">%s<span>', $description );
	
}

Et mes sections s’affichent correctement.

Regrouper les fonctions « callback » pour les champs

Je vais faire la même chose maintenant pour les champs, en remplaçant les 4 fonctions correspondant aux 4 champs par une seule.

En faisant afficher l’array des arguments transmis à la fonction « callback » des champs, j’ai vu que cet array est composé comme suit :

Array
(
    [field_id] => field-1-1
    [label] => Field One
    [field_callbk] => field_1_1_callback
    [menu_slug] => my-plugin
    [section_name] => section-1
    [type] => text
    [helper] => help 1
    [default] => 
)

une fonction « callback » pour les champs définis actuellement (des textes) contient les 4 lignes suivantes :

$settings = (array) get_option( 'my-plugin-settings' );
$field = "field_1_1";
$value = esc_attr( $settings[$field] );
	
echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";

Il va falloir que je remplace tout ce qui est spécifique au champs 1-1 par des variables, pour que ça s’applique à tous les champs :

$settings = (array) get_option( 'my-plugin-settings' );

Cette ligne va chercher la valeur enregistrée pour toutes les options d’un nom défini lorsque nous avons exécuté la commande ‘register_setting’, au début de la fonction clea_add_button_admin_init() . C’est une valeur fixe que je laisse telle quelle pour l’instant (ce n’est certainement pas une bonne idée de laisser un nom si générique, sans préfixe spécifique).

$field = "field_1_1";

Là on voit que « field_1_1 » correspond au contenu de $args[field_id].

$value = esc_attr( $settings[$field] );

Cette ligne lit la valeur attribuée à l’option et la met dans la variable « value ». On ne la change pas puisque $field n’est plus fixe.

echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";

Ne change pas non plus. Ma fonction clea_add_button_settings_field_callback( $args  ) va donc contenir quasiment la même chose qu’avant mais elle sera utilisable par tous les champs :

function clea_add_button_settings_field_callback( $args  ) {

	$settings = (array) get_option( 'my-plugin-settings' );
	$field = $args[field_id] ;
	$value = esc_attr( $settings[$field] );
	
	echo "<input type='text' name='my-plugin-settings[$field]' value='$value' />";	
	
}

Maintenant je supprime les 4 fonctions callback des 4 champs définis et dans la définition des champs, je remplace le contenu de ‘field_callbk’ par le nom de la fonction collective.

J’obtiens bien l’affichage de mon formulaire, mais pas des valeurs déjà enregistrées. Et il y a 4 PHP notices. La première notification signale Undefined index: field-1-1  en ligne 128 de clea-add-button-settings-page.php. Les trois autres sont identiques et portent sur les 3 autres champs.

C’est en fait « normal » : je me suis trompée en définissant l’array des champs. J’ai mis des id de type ‘field-1-1’ alors que Anna, l’auteure du Gist dont je m’inspire utilisais field_1_1… Il m’a suffi d’enregistrer une valeur pour que tout rentre dans l’ordre.

Et voilà, j’obtiens exactement le même résultat qu’Anna mais même si j’ajoute des dizaines de paramètres, les fonctions qui les restituent resteront uniques. C’est plus « élégant », mais surtout ça réduit fortement le risque d’erreur si j’ajoute un nouveau paramètre car le code est beaucoup plus lisible.

le fichier ‘clea-add-button-settings-page.php’ (le seul modifié) est dans ce « commit », sur github. Vous pouvez également récupérer l’extension à ce stade dans ce fichier zip : clea-add-button V0.1 (zip).

Et maintenant ?

Dans des articles à venir, il faudra voir comment ajouter d’autres types de champs, comment charger des feuilles de style, comment faire utiliser un template de l’extension…

Mais d’abord, il faut voir comment on peut plus facilement déboguer. C’est l’objet du prochain article de cette série [the-series).

Données d’une box domotique Eedomus dans une page PHP

Données d’une box domotique Eedomus dans une page PHP

Dans ce second article de la série , je veux afficher une température stockée dans ma box domotique Eedomus + à mon tableau de bord (en PHP, sur Raspberry Pi).

Cet article a été complété le 8 mai 2017.

De quoi je pars ?

d’une page index.php servie par un Raspberry Pi, comme indiqué dans le premier article  de cette série.

l’API de la box Eedomus

Elle est bien documentée ici : API eedomus.

Obtenir une clé d’API Eedomus

Dans https://secure.eedomus.com/, cliquer sur configurer puis « mon compte ».

  1. Cocher la case « autoriser l’API via http » et « sauver ».
  2. Cliquer sur « consulter vos identifiants »

API Eedomus - 1

On obtient ainsi les valeurs de api_user et api_secret qui nous permettront de nous connecter. On accède aussi à un formulaire qui va nous permettre de construire la requête.

Faire une requête pour un capteur spécifique

J’utilise le formulaire précédent puis je copie la requête HTTP obtenue :

API Eedomus - 2

La requête est de type (remplacer api_user et api_secret) :

http://api.eedomus.com/get?api_user=xxx&api_secret=yyy&action=periph.caract&periph_id=166280

Lorsque je la tape dans un navigateur internet, j’obtiens la chaîne JSON suivante :

{ "success": 1, "body":{"periph_id": "166280", "name": "Nest - Température Couloir RdC", "last_value": "24.5", "last_value_text": "", "last_value_change": "2016-08-15 15:50:55"}}

Pour lire cette donnée dans mon fichier PHP, je me suis inspirée de l’exemple donné à la fin du document  API eedomus.

J’ai intégré le code suivant à ma page index.php :

<h3>Autres informations</h3>

		<?php include 'eedomus.php';?>

		<!-- nest temperature ID 166280 -->
		<p>couloir : <?php echo al_get_eedomus_value( 166280, 'last_value' ) ; ?> °C</p>

Et dans le fichier eedomus.php , j’ai intégré une fonction al_get_eedomus_value  :

<?php

// définition des variables
$api_user = 'xxx'; // a récupérer sur votre compte eedomus
$api_secret = 'yyy'; // a récupérer sur votre compte eedomus

/******************************************************************
* appel de l'API eedomus en PHP pour récupérer une donnée
*
* $id = 166280
* $val = 'last_value'
* retournera la température actuellement mesurée par le thermostat Nest
*
*****************************************************************/ 

function al_get_eedomus_value( $id, $val ) {
	
	global $api_user ;
	global $api_secret ;
	
	// construction de l'URL de l'API
	$url = "http://api.eedomus.com/get?action=periph.caract";
	$url .= "&api_user=" . $api_user;
	$url .= "&api_secret=" . $api_secret;
	$url .= "&periph_id=" . $id;

	// appel de l'API
	$result = file_get_contents($url);

	// on controle le résultat
	if (strpos($result, '"success": 1') == false)
	{
	  echo "Une erreur est survenue : [".$result."]";
	}
	else
	{
	  $result = json_decode( $result, true ) ; // true transforme $result en un array
	  $retour = $result['body'][$val];
	  return $retour ;
	}	
	
}

Le résultat est maintenant affiché sur mon tableau de bord :

Affichage PHP de capteur via l'API Eedomus

Mais les données ne sont mises à jour que si je rafraichis le tableau de bord manuellement.

Mettre à jour automatiquement les données de la page PHP

Cette question sur StackOverflow explique comment recharger une page toutes les 30 secondes. Il suffit d’ajouter <meta http-equiv= »refresh » content= »30″ />  en haut de la page PHP.

Comme 15 minutes = 900 secondes, j’ai intégré la ligne  <meta http-equiv= »refresh » content= »900″ /> en haut de ma page PHP. Le haut contient donc maintenant :

<!DOCTYPE html>
<html lang="fr">

<head>
	<meta charset="utf-8">
	<title>Nautilus</title>
	<meta name="viewport" content="width=device-width,initial-scale=1" />
	<link href="A-style.css" rel="stylesheet" media="all" type="text/css"> 
	<meta http-equiv="refresh" content="900" />
</head>

Complément (8 mai 2017)

J’ai affiné le contenu de ma page php pour avoir automatiquement la liste des périphériques de l’eedomus. C’est un préalable à un tableau de bord qui lirai automatiquement toutes les données de l’eedomus.

Obtenir la liste des périphériques

Dans le code ci-dessous, la fonction eedomus_url génère les url correctes pour :

  • vérifier qu’on se connecte correctement à la box Eedomus ;
  • Obtenir la liste des périphériques de la box Eedomus ;
  • Lire la valeur d’un périphérique donné de la box Eedomus

la fonction ald_decode_french  permet de lire correctement des textes encodés en français, avec des caractères spéciaux.

	<?php
	
	function eedomus_url( $action, $user_id, $pw ) {
		/* 
		* eedomus_url( "&action=auth.test", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=auth.test
		* eedomus_url( "&action=periph.list", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=periph.list		
		* eedomus_url( "&action=periph.caract&periph_id=156595", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=periph.caract&periph_id=156595
		*
		* ces 3 url renvoient bien une info en Json, en provoenance de l'eedomus dans un navigateur
		*/
		
		$eedomus_get = "https://api.eedomus.com/get?api_user=" ;
		$eedomus_get .= $user_id ;
		$eedomus_get .= "&api_secret=" ;
		$eedomus_get .= $pw ;
		$eedomus_get .= $action ;

		return $eedomus_get ;
	}

	$ee_test = eedomus_url( "&action=auth.test", $ee_apiuser, $ee_passwd ) ; 
	$ee_list = eedomus_url( "&action=periph.list", $ee_apiuser, $ee_passwd ) ; 
	$ee_156595 = eedomus_url( "&action=periph.caract&periph_id=156595", $ee_apiuser, $ee_passwd ) ; 

	function ald_decode_french( $string ) {
		
		$contents = utf8_encode( $string ); 
		$results = json_decode( $contents ); 
		return $results ;
		
	}

Si j’utilise la fonction ald_decode_french  sans la partie ‘utf8_encode’, le résultat est vide car les accents provoquent des erreurs. Ci-dessous, dans l’image, à gauche on a le résultat de <pre><?php print_r ( file_get_contents( $ee_list ) ) ; ?> </pre>  et à droite le résultat de <pre><?php print_r ( ald_decode_french( file_get_contents( $ee_list ) ) ); ?> </pre> . Les accents sont correctement restitués dans la version de droite.

Utilisation de utf8_encode pour des accents dans du Json

Et maintenant ?

Je vais maintenant voir comment un Raspberry Pi peut transmettre une valeur mesurée à l’Eedomus. Ce sera l’objet du prochain article de cette série . Ensuite, je pourrai utiliser le présent tutoriel pour l’afficher sur mon tableau de bord.

Données de Wunderground dans une page PHP

Données de Wunderground dans une page PHP

Aujourd’hui, je veux lire des données issues d’une station météo proche de chez moi via l’API wunderground. Je souhaite qu’elles s’affichent automatiquement dans mon tableau de bord domotique (une page PHP servie par un Raspberry Pi).

De quoi je pars ?

J’ai réglé un Raspberry Pi pour servir une page PHP qui affiche le flux de deux caméras de surveillance (voir l’article Récapitulatif : Raspberry Pi, motion et deux caméras).

Maintenant, je veux ajouter des données issues de capteurs externes ou non. Je commence par insérer des données issues d’une station météo externe, via l’API de wunderground.

Une API ?

J’ai suivi le très bon cours en ligne sur les API de Emily REESE, sur OpenClassRoom ici.

Une API, ou  « Application Programming Interface », est une interface entre un utilisateur et une application. Un menu constitue l’API d’un restaurant par exemple. En informatique, lorsqu’on parle d’API, on parle en général d’API REST (cf sur wikipedia). Elles utilisent des instructions HTTP.

En général les requêtes HTTP à des API REST obtiennent une réponse en JSON (cf  sur wikipedia).

L’API de wunderground

Wunderground est un site qui met à disposition des informations météos. Les personnes qui possèdent une station météo peuvent lui communiquer des données. On a ainsi accès à des données très locales.

J’ai identifié une station locale à Plonéour-Lanvern. Je veux en extraire des données.

Je me suis créé un compte sur Wunderground et j’ai généré une clé API : voir cet article en français pour savoir comment faire.

Avec la console apigee intégrée à wunderground ici, j’ai pu déterminer que la requête est sous la forme :

http://api.wunderground.com/api/APIkey/conditions/q/pws:Station-ID.json

avec APIkey qui est ma clé API (elle ressemble à « b8e924a8f008b81e« ) et Station-ID est égale à « IPLONOUR3 » pour la station qui m’intéresse. On obtient l’ID de la station en cliquant  sur le nom de la station (flèche rouge dans la copie d’écran ci-dessous). j’accède alors à une page dont l’adresse est https://www.wunderground.com/personal-weather-station/dashboard?ID=IPLONOUR3#history. J’ai donc l’ID.

ID d'une station sur Wunderground

Faire une requête en PHP

Je me suis inspirée de cette question sur StackOverflow pour créer ma requête.

Si je met http://api.wunderground.com/api/APIkey/conditions/q/pws:IPLONOUR3.json (remplacer APIkey par votre clé API) dans un navigateur web, j’obtiens en retour une chaîne JSON :

{
  "response": {
  "version":"0.1",
  "termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
  "features": {
  "conditions": 1
  }
	}
  ,	"current_observation": {
		"image": {
		"url":"http://icons.wxug.com/graphics/wu2/logo_130x80.png",
		"title":"Weather Underground",
		"link":"http://www.wunderground.com"
		},
		"display_location": {
		"full":"Ploneour-Lanvern, France",
		"city":"Ploneour-Lanvern",
		"state":"",
		"state_name":"France",
		"country":"FR",
		"country_iso3166":"FR",
		"zip":"00000",
		"magic":"34",
		"wmo":"07201",
		"latitude":"47.900333",
		"longitude":"-4.287129",
		"elevation":"60.00000000"
		},
		"observation_location": {
		"full":"Rue François de Châteaubriand, Plonéour-Lanvern, ",
		"city":"Rue François de Châteaubriand, Plonéour-Lanvern",
		"state":"",
		"country":"FR",
		"country_iso3166":"FR",
		"latitude":"47.900333",
		"longitude":"-4.287129",
		"elevation":"206 ft"
		},
		"estimated": {
		},
		"station_id":"IPLONOUR3",
		"observation_time":"Last Updated on August 15, 3:01 PM CEST",
		"observation_time_rfc822":"Mon, 15 Aug 2016 15:01:45 +0200",
		"observation_epoch":"1471266105",
		"local_time_rfc822":"Mon, 15 Aug 2016 15:04:53 +0200",
		"local_epoch":"1471266293",
		"local_tz_short":"CEST",
		"local_tz_long":"Europe/Paris",
		"local_tz_offset":"+0200",
		"weather":"Clear",
		"temperature_string":"79.9 F (26.6 C)",
		"temp_f":79.9,
		"temp_c":26.6,
		"relative_humidity":"48%",
		"wind_string":"Calm",
		"wind_dir":"NE",
		"wind_degrees":50,
		"wind_mph":0.6,
		"wind_gust_mph":"5.0",
		"wind_kph":1.0,
		"wind_gust_kph":"8.0",
		"pressure_mb":"1018",
		"pressure_in":"30.06",
		"pressure_trend":"0",
		"dewpoint_string":"59 F (15 C)",
		"dewpoint_f":59,
		"dewpoint_c":15,
		"heat_index_string":"81 F (27 C)",
		"heat_index_f":81,
		"heat_index_c":27,
		"windchill_string":"NA",
		"windchill_f":"NA",
		"windchill_c":"NA",
		"feelslike_string":"81 F (27 C)",
		"feelslike_f":"81",
		"feelslike_c":"27",
		"visibility_mi":"N/A",
		"visibility_km":"N/A",
		"solarradiation":"--",
		"UV":"-1","precip_1hr_string":"-999.00 in ( 0 mm)",
		"precip_1hr_in":"-999.00",
		"precip_1hr_metric":" 0",
		"precip_today_string":"0.00 in (0 mm)",
		"precip_today_in":"0.00",
		"precip_today_metric":"0",
		"icon":"clear",
		"icon_url":"http://icons.wxug.com/i/c/k/clear.gif",
		"forecast_url":"http://www.wunderground.com/global/stations/07201.html",
		"history_url":"http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IPLONOUR3",
		"ob_url":"http://www.wunderground.com/cgi-bin/findweather/getForecast?query=47.900333,-4.287129",
		"nowcast":""
	}
}

Les données qui m’intéressent sont :

  • « current_observation » -> « display_location » -> « city »
  • « current_observation » -> « temp_c »
  • « current_observation » -> « relative_humidity »
  • « current_observation » -> « wind_gust_kph »
  • « current_observation » -> « forecast_url »

En PHP, j’ajoute le code suivant à mon fichier index.php :

	<section class="capteurs">

		<h3>wunderground</h3>
		<?php

		// http://stackoverflow.com/questions/20044579/how-to-get-a-value-from-wunderground-json
		$json_string = file_get_contents("http://api.wunderground.com/api/APIkey/conditions/q/pws:IPLONOUR3.json");
		$parsed_json = json_decode($json_string);
		$location = $parsed_json->{'current_observation'}->{'display_location'}->{'city'};
		$temp_c = $parsed_json->{'current_observation'}->{'temp_c'};
		$humidity = $parsed_json->{'current_observation'}->{'relative_humidity'};
		$wind_gust_km = $parsed_json->{'current_observation'}->{'wind_gust_kph'};
		$forecast_url = $parsed_json->{'current_observation'}->{'forecast_url'};
		
		echo "A ${location} : ${temp_c} °C, ${humidity} HR, rafales à ${wind_gust_km} km/h - <a target='_blank' href='${forecast_url}'>prévisions</a>. \n";

		?>
	</section>

Et maintenant mon tableau de bord affiche la température, l’humidité, la vitesse des rafales de vent et un lien vers les prévisions météo locales :

Affichage PHP de données via l'API Wunderground

Et maintenant ?

Je vais maintenant intégrer une donnée issue de ma box domotique Eedomus. Ce sera l’objet du second article de cette série .