Personnaliser l’admin bar

class CustomizeAdminBar {

	public static function hooks() {
		add_action( 'wp_before_admin_bar_render', array( __CLASS__, 'customize_admin_bar' ) );
	}

	public static function customize_admin_bar() {
		global $wp_admin_bar;
		global $current_user;

		//Suppression de l'entrée principale de l'admin bar pour les utilisateurs "Subscriber":
		if ( in_array( 'subscriber', $current_user->roles ) ) {
			$wp_admin_bar->remove_menu( 'site-name' );
		}

		//Ajout d'un élément à l'admin bar (lien vers une page "My account") :
		$wp_admin_bar->add_menu( array(
			'id' => 'user-page-my-account',
			'parent' => false,
			'title' => 'My account',
			'href' => get_option( 'siteurl' ) . '/my-account/',
		) );

	}

}

CustomizeAdminBar::hooks();

 

https://codex.wordpress.org/Plugin_API/Action_Reference/wp_before_admin_bar_render

$wp_admin_bar->remove_menu() : Pour trouver l’id de l’entrée du menu à supprimer (ici ‘site-name’), regarder l’attribut id de l’élément dans la source HTML.

Plugin ACF v5 : créer des pages d’options

class AcfOptionsPages{
	 
	public static function hooks(){
		add_action('plugins_loaded', array(__CLASS__,'plugins_loaded'));
	}
	 
	public static function plugins_loaded(){
		
		if( function_exists('acf_add_options_page')) { 
	 
			acf_add_options_page();
			acf_add_options_sub_page('Header');
			acf_add_options_sub_page('Footer');
		 
			//Optionnel : régler qui a droit de voir la page d'options :
			acf_set_options_page_capability('edit_posts');
			
			//Optionnel : libellé de la page d'option mère dans le menu
			acf_set_options_page_menu('My options'); 
			
			//Optionnel : libellé des sous pages d'options dans le menu
			acf_update_options_page(array(
				'menu_slug'		=> 'acf-options-header',
				'menu_title'	=> 'My header'
			));
			
			//Optionnel : titre affiché dans la page d'option mère :
			acf_set_options_page_title('My options title');
			
			//Optionnel : titre affiché dans les sous page d'options :
			acf_update_options_page(array(
				'menu_slug'		=> 'acf-options-header',
				'page_title'	=> 'My header option page title'
			));
		}
		
	}
	
	 
}
AcfOptionsPages::hooks();

Gestion des roles et capabilities WordPress

<?php
/*
Plugin Name: Test WordPress roles and capabilities
Description: Basic use of WordPress roles and capabilities management API
Version: 1.0
*/

if( !class_exists('wp_test_roles_and_capabilities') ){
class wp_test_roles_and_capabilities{
	
	public static function hooks(){
		
		//Création des roles dans "admin_init" uniquement pour le débug :
		//utiliser le hook d'activation de plugin lors de l'utilisation finale,
		//comme expliqué ici : http://codex.wordpress.org/Function_Reference/add_role
		add_action('admin_init',array(__CLASS__,'create_role'));
		
		//To add / remove menu panels :
		add_action('admin_menu', array(__CLASS__,'adjust_wp_menu'), 999);
	}	
	
	public static function create_role(){
		
		//Pour le détail des roles et capabilities, voir :
		//http://codex.wordpress.org/Roles_and_Capabilities
		
		//Récupération des capabilities d'un role existant :
		//$author_role = get_role('author');
		//$author_role['capabilities']; 

		//Ajouter/supprimer une capability à un role existant :
		//$author_role->add_cap('edit_others_posts');
		//$author_role->remove_cap('edit_others_posts');
		
		//Création d'un nouveau role "Contributeur spécial" pour tester les capabilities :
		remove_role( 'special_contributor' );
		$new_role = add_role(
				'special_contributor',
				'Contributeur spécial',
				array(
						
					//Default WP "Author" role capabilities :
					'read' => true,
					'edit_posts' => true,
					'delete_posts' => true,
					'publish_posts' => true,
					'delete_published_posts' => true,
					'edit_published_posts' => true,
					'upload_files' => true,

					//Additional roles :
		
					//Droits de création de nouvelles catégories (ou hierachical custom taxonomies). 
					//Permet de masquer le lien "Add new category" et la gestion de catégorie dans l'onglet "Posts" du BO.
					//N'agit pas sur la création de tags.
					'manage_categories' => false, 
						
					//Widgets :
					//'edit_theme_options' ajoute le menu "Appearance"
					//Voir ci-dessous self::adjust_wp_menu() pour le masquage des éléments du menu "Appearance" non souhaités
					'edit_theme_options' => true, 
					
				)
		);
		
		if( $new_role == null ){
			echo 'Erreur à la création du role "Contributeur spécial"';
		}

	}
	
	public static function adjust_wp_menu(){
		global $current_user;
		
		//Pour voir les identifiants des éléments de menu et sous menu : 
		//global $menu,$submenu;
		//var_dump($menu,$submenu);
		
		//On ne laisse que le sous menu widgets du menu "Appearance" pour les utilisateurs ayant le role "Contributeur spécial" :
		if( $current_user && in_array('special_contributor', $current_user->roles) ){
			remove_submenu_page( 'themes.php', 'themes.php' );
			remove_submenu_page( 'themes.php', 'customize.php' );
			//remove_submenu_page( 'themes.php', 'widgets.php' );
			remove_submenu_page( 'themes.php', 'nav-menus.php' );
			remove_submenu_page( 'themes.php', 'custom-header' );
			remove_submenu_page( 'themes.php', 'custom-background' );
			remove_submenu_page( 'themes.php', 'theme-editor.php' );
		}
	}
}
wp_test_roles_and_capabilities::hooks();
}

Voir http://codex.wordpress.org/Roles_and_Capabilities pour le détail des roles et capabilities WordPress.

Création d’un role
Utilisation la fonction add_role() pour créer un nouveau role avec des capabilities personnalisées.

Permissions de création de catégories :
Utiliser la capability « manage_categories » pour gérer la permission d’ajout de catégorie (voir create_role() dans le plugin ci-dessus).
Elle fonctionne pour les custom taxonomies hiérarchiques (mais pas pour les taxonomies non hiérarchiques, comme les tags, pour lesquelles on peut toujours ajouter un terme depuis l’édition d’article).

Permissions de création de tags :
Dans l’édition d’articles, permettre à un utilisateur d’assigner des tags sans pouvoir en créer n’est pas possible avec l’interface par défaut de la gestion de tags wp.
> il faut remplacer la métabox de tag par une autre customisée qui ne permet pas l’ajout de tag, via un plugin.

Test et ajout de capabilities :

  • la fonction permettant de savoir si un utilisateur a une capability est current_user_can(‘capability_a_tester’) qui retourne vraie si l’utilisateur a bien la capability dans son role.
  • on peut « inventer » des capabilities : pas de fonction spécifique pour créer une capability, juste tester if(current_user_can(‘notre_capability’)) pour afficher des métabox / panels de menus / widgets seulement pour les utilisateurs ayant un role incluant la capability « notre_capability ».

Affichage/masquage d’une métabox suivant l’utilisateur :

  • si c’est une métabox créée par un plugin à nous, c’est simple : on peut créer une capability (ou utiliser une capability existante) et la tester pour voir si on affiche ou non la métabox.
  • si c’est une métabox d’un autre plugin ou métabox par défaut wp, on peut soit faire un remove_meta_box, soit faire un hack CSS si pas d’autre moyen.

Affichage/masquage d’un panel ou sous panel BO (Comme « Posts », « Settings », « Tools » …) :

  • si c’est un panel créé par un plugin à nous, c’est simple : on peut créer une capability (ou utiliser une capability existante) et la tester pour voir si on affiche ou non le panel.
  • si c’est un panel d’un autre plugin ou panel par défaut wp on utilise remove_menu_page() ou remove_submenu_page()

Affichage/masquage des widgets :
Pour maîtriser l’apparition du panel « Widgets » dans le menu : utilisation de la capability « edit_theme_options » et du masquage de sous menus (voir adjust_wp_menu() dans la classe ci-dessus)
Pour masquer des widgets spécifiques :

  • Si c’est un widget d’un plugin à nous, même principe que pour les métaboxes et panels
  • Sinon, utilisation de la fonction unregister_widget()

Squelette plugin se chargeant dans le hook plugins_loaded

<?php
/*
Plugin Name: My contents
Description: Create custom contents 
Version: 1.0
*/

if( !class_exists('MyContents') ){
class MyContents{
			
	public static function hooks(){
		add_action('plugins_loaded', array(__CLASS__,'plugins_loaded'));
	}
	
	public static function plugins_loaded(){
		self::lib_require();
	}
	
	protected static function lib_require(){
		require_once(dirname(__FILE__) .'/lib/my-content-1.php');
		require_once(dirname(__FILE__) .'/lib/my-content-2.php');
	}
	
}
MyContents::hooks();
}

Migration WordPress en utilisant Search and replace d’interconnectit

Exemple pour migration d’un site source « http://local.mon-site.com » vers un site destination « http://www.mon-site.com » :

  • copie des sources wordpress
  • copie de la base de données mysql
  • changer DB_NAME, DB_USER, DB_PASSWORD, DB_HOST dans wp_config.php

A ce point, la nouvelle url (http://www.mon-site.com) doit marcher, mais dès qu’on clique sur un lien on se retrouve sur l’ancien site : il faut remplacer en base les occurences de « http://local.mon-site.com » par « http://www.mon-site.com ». Et pour bien faire, il faut les remplacer dans toutes les tables (et non seulement dans wp_options), y compris dans les données sérialisées. Pour ça on utilise ici le script « Search and replace » d’interconnectit : https://interconnectit.com/products/search-and-replace-for-wordpress-databases/.

  • télécharger le zip ici : https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
  • dézipper et copier le répertoire “Search-Replace-DB-master” à la racine du nouveau wordpress.
  • se rendre à http://votre-site.com/Search-Replace-DB-master/ : une page search/replace doit apparaître
  • entrer dans les 2 champs d’édition : Replace “http://local.mon-site.com” with “http://www.mon-site.com”
  • vérifier les champs d’accès à la base (récupérés du wp-config.php automatiquement)
  • c’est tout, cliquer sur “Dry run” pour faire un test sans affecter la bdd, puis sur “Live run” pour appliquer réellement les modifs si c’est bon.
  • pour la sécurité, supprimer le script Search-Replace-DB-master

Normalement le site est alors monté et fonctionnel sur http://www.mon-site.com

Création d’un widget avec upload d’image utilisant la boite de dialogue média de WordPress

Déclaration d’une sidebar

register_sidebar( array('name'=>'My sidebar name', 'id' => "my_sidebar_id" ) );

Déclaration du widget

add_action('widgets_init', 'my_widget_init');
function my_widget_init() {
    register_widget("My_Widget"); //My_Widget is the class defined hereunder, extending WP_Widget 
}

Insertion dans le template

<?php if( dynamic_sidebar('my_sidebar_id') ): ?>
    <!-- Display something after the sidebar widgets contents -->
<?php else : ?>
    <!-- Display something if the sidebar is not defined -->
<?php endif; ?>

Le Widget

Note : javascript et styles sont inclus directement dans le template html ici, la bonne pratique serait de les déplacer dans des fichiers séparés inclus par wp_enqueue_script() / wp_enqueue_style().

<?php

//Enqueue WordPress media JS to handle image upload dialog box
add_action('admin_enqueue_scripts', 'my_widget_scripts');
function my_widget_scripts() {
    global $pagenow;
    if( $pagenow == 'widgets.php' ){
        wp_enqueue_media();
    }
}

//Create a class extending WP_Widget
class My_Widget extends WP_Widget {

    /**
     * Register widget with WordPress.
     */
    public function __construct() {
        parent::__construct(
            'my_widget', // Base ID
            'My widget', // Name
            array( 'description' => __( 'My widget description' ) ) // Args
        );
    }

    /**
     * Widget Front-end display
     */
    public function widget( $args, $instance ) {
        
        $img = $instance['img'];
        $url = $instance['url'];
        $title = $instance['title'];
       
        if( !empty($img) ){
            ?>
                <a href="<?php echo $url ?>" title="<?php echo $title ?>">
                    <div class="my-widget-img" style="background-image: url('<?php echo $img ?>');"></div>
                </a>
            <?php
        }
    }

    /**
     * Sanitize widget form values as they are saved.
     */
    public function update( $new_instance, $old_instance ) {
        $instance = array();

        $instance['img'] = strip_tags( $new_instance['img'] );
        $instance['url'] = strip_tags( $new_instance['url'] );
        $instance['title'] = strip_tags( $new_instance['title'] );

        return $instance;
    }

    /**
     * Back-end widget form.
     */
    public function form( $instance ) {
        
        $img = isset( $instance[ 'img' ] ) ? $instance[ 'img' ] : '';
        $url = isset( $instance[ 'url' ] ) ? $instance[ 'url' ] : '';
        $title = isset( $instance[ 'title' ] ) ? $instance[ 'title' ] : '';
        
        $img_field_id = $this->get_field_id('img');
        
        ?>
        
        <p>
            <label for="<?php echo $img_field_id ?>">Image :</label><br/>
            <input class="upload_image" id="<?php echo $img_field_id ?>" type="hidden" name="<?php echo $this->get_field_name('img') ?>" value="<?php echo $img ?>" />
            <input class="upload_image_button" id="<?php echo $img_field_id ?>_button" type="button" value="Charger une image" data-field-id="<?php echo $img_field_id ?>" />
            <div id="<?php echo $img_field_id ?>_img" class="upload_image_wrapper">
                <?php if( !empty($img) ):?>
                    <img src="<?php echo $img ?>" />
                    <a href="#" class="upload_image_delete" data-field-id="<?php echo $img_field_id ?>">Supprimer l'image</a>
                <?php endif ?>
            </div>
        </p>
        
        <p>
            <label for="<?php echo $this->get_field_id('url') ?>">URL du lien :</label>
            <input class="widefat" id="<?php echo $this->get_field_id('url') ?>" name="<?php echo $this->get_field_name('url') ?>" type="text" value="<?php echo esc_attr($url) ?>" />
        </p>
        
        <p>
            <label for="<?php echo $this->get_field_id('title') ?>">Message au survol de l'image :</label>
            <input class="widefat" id="<?php echo $this->get_field_id('title') ?>" name="<?php echo $this->get_field_name('title') ?>" type="text" value="<?php echo esc_attr($title) ?>" />
        </p>
        
        <style>
            .upload_image_wrapper img{ width:100% }
        </style>
        
        <script>
                //Le code suivant doit normalement être positionné dans un fichier Javascript séparé,
                //notamment dans le cas où on positionne plusieurs fois le widget dans l'interface, 
                //sinon le "jQuery(document).ready()" ci-dessous est lancé autant de fois que le nombre  
                //de widgets positionnés. Ca fonctionne car on fait des "unbind()" mais 
                //ce n'est pas optimisé.

                var upload_image_custom_uploaders = upload_image_custom_uploaders || {};
            
                jQuery(document).ready(function($){
 
                        $('.upload_image_button').unbind().click(function(e) {
 
                                e.preventDefault();
 
                                var field_id = $(this).data('field-id');
                    
                                //If the uploader object has already been created, reopen the dialog
                                if( upload_image_custom_uploaders.hasOwnProperty( field_id ) ) {
                                        upload_image_custom_uploaders[field_id].open();
                                        return;
                                }
              
                                //Extend the wp.media object
                                var upload_image_custom_uploader = wp.media.frames.file_frame = wp.media({
                                        title: 'Choisir une image',
                                        button: {
                                            text: 'Choisir cette image'
                                        },
                                        multiple: false
                                });
                    
                                upload_image_custom_uploaders[field_id] = upload_image_custom_uploader;
              
                                //When a file is selected, grab the URL and set it as the text field's value
                                upload_image_custom_uploader.on('select', function() {
                                        attachment = upload_image_custom_uploader.state().get('selection').first().toJSON();
                                        $('#'+ field_id).val(attachment.url);
                                        $('#'+ field_id +'_img').html('<img src="'+ attachment.url +'" />');
                                });
              
                                //Open the uploader dialog
                                upload_image_custom_uploader.open();
              
                        });
 
                        $('.upload_image_delete').unbind().click(function(e){
                                e.preventDefault();
                                var field_id = $(this).data('field-id');
                                $('#'+ field_id).val('');
                                $('#'+ field_id +'_img').html('');
                        });
              
                });
        </script>
        
        <?php
    }

}

Externalisation du javascript

Dans le cas où l’on souhaite positionner le script dans un fichier séparé, il faut utiliser l’event delegation de jQuery puisque le HTML est rechargé dynamiquement.
Pour cela, remplacer :

$('.upload_image_button').unbind().click(function(e) {

par :

$('#wpbody,#customize-controls').on( 'click', '.upload_image_button', function(e) {

et :

$('.upload_image_delete').unbind().click(function(e){

par :

$('#wpbody,#customize-controls').on( 'click', '.upload_image_delete', function(e){

Doc

http://codex.wordpress.org/Widgets_API
http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/

Date prenant en compte le temps local

Time

Pour avoir le temps local et non pas le temps universel :

time()

devient :

current_time('timestamp')

Date

date(...)

devient :

date(...,current_time('timestamp'))

A la main

Et pour faire le calcul à la main :

time() + get_option('gmt_offset') * 3600

Afficher la page 404 en cas d’erreur lors d’une redirection template_redirect

class products_settings{
	 
	public static function hooks(){
		add_action('template_redirect',array(__CLASS__,'template_redirect'));
	}

	public static function template_redirect(){
		global $wp_query;
		if( "Tests sur $wp_query->query_vars[] échouent" ){
			//Affichage de la page 404 :
			global $wp_query;
			$wp_query->set_404();
			status_header(404);
			get_template_part(404);
			exit();
		}
	}

}

products_settings::hooks();