WordPress: Personnaliser la mise à jour des plugins

Pour tous les utilisateurs de WordPress, les mises à jour automatiques constituent un réel progrès, puisqu’elles font disparaître les opérations fastidieuses de téléchargement, de décompression, puis d’upload via FTP, de la nouvelle version. Suite à une mise à jour cauchemardesque de mon plugin EG-Series, je me suis intéressé aux mécanismes de mise à jour de WordPress, et au moyen de les personnaliser sur les plateformes de développement.

Fonctionnement des mises à jour

Avant d’effectuer les modifications, il faut comprendre comment fonctionne WordPress:

  • Régulièrement, au fil des requêtes, WordPress lance une fonction pour vérifier les mises à jour (via un système de cron),
  • Cette fonction parcourt la liste des plugins,
  • Pour chaque plugin, elle lance une requête sur wordpress.org, puis mémorise
    • La date de la vérification,
    • La liste des plugins testés,
    • Ceux dont il existe une mise à jour.
  • WordPress affiche ensuite, dans les rubriques correspondantes (menu « Mises à jour », menu « Extension », …)

Ces informations sont stockées dans la table wp_options de la base de données, dans un enregistrement appelé _site_transient_update_plugins. On peut accéder à cet enregistrement dans un plugin avec la fonction get_site_transient( 'update_plugins' ).

« Réinitialiser » la mise à jour

Si vous souhaitez déclencher plus rapidement la recherche de mise à jour, ou si vous avez des problèmes de « faux » messages, il suffit d’effacer l’enregistrement _site_transient_update_plugins:

  • A partir de PHP, dans un plugin, avec la commande delete_site_transient( 'update_plugins' ),
  • A partir de la base de données, avec la requête Delete from wp_options where option_name="_site_transient_update_plugins".

Assez fréquents avec les plus anciennes versions, les problèmes de mises à jour des plugins, sont devenus beaucoup plus rares aujourd’hui.

Exclure des plugins de la mise à jour

L’astuce vient de Mark Jaquith.

Il n’existe pas de hook, ou de filtre, dans les fonctions de mise à jour, permettant d’effectuer cette exclusion. L’astuce consiste donc à intercepter les requêtes HTTP de WordPress vers l’extérieur, de vérifier qu’il s’agit bien d’une requête de mise à jour, puis de supprimer le plugin de la liste

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function exclude_plugin_xxx( $r, $url ) {
	if ( 0 !== strpos( $url, 'http://api.wordpress.org/plugins/update-check' ) )
		return $r; // Il ne s'agit pas d'une requête d'update. Nous sortons immédiatement
 
	$plugins = unserialize( $r['body']['plugins'] );
	// On efface le plugin de la liste des plugins
	unset( $plugins->plugins[ plugin_basename( __FILE__ ) ] );
 
	// On efface le plugin de la liste des plugins actifs
	unset( $plugins->active[ array_search( plugin_basename( __FILE__ ), $plugins->active ) ] );
 
	$r['body']['plugins'] = serialize( $plugins );
	return $r;
}
add_filter( 'http_request_args', 'exclude_plugin_xxx', 5, 2 );

Avec cette méthode, il faut créer une fonction par plugin à exclure. Notez que la méthode fonctionne également pour la mise à jour des thèmes.

Simuler localement, les mises à jour

Pour gérer les mises à jour, en local, nous avons deux parties distinctes:

  • Une zone ou déposer les nouvelles versions, accessible en http,
  • Un mécanisme qui fait croire à WordPress, que notre plugin a effectivement besoin d’une mise à jour.
Etape 1: création du dépôt pour les fichiers ZIP

Pour la première partie, quel que soit le serveur Web utilisé, la méthode la plus simple est de créer un répertoire à la racine de votre installation WordPress.

Par exemple: Si WordPress se trouve dans /temp/dev/htdocs/wp330, et que ce répertoire est visible avec l’URL http://localhost/wp330, alors il suffit de créer un répertoire /temp/dev/htdocs/wp330/repository, qui sera visible avec l’url http://localhost/wp330/repository.

Vous pouvez déposer ici, la nouvelle version de vos plugins, sous la forme de fichiers ZIP, sous la forme [nom du plugin].[nouvelle version].zip.

Etape 2: Indiquer les mises à jour à WordPress

Pour cette étape, nous devons analyser ce que contient l’enregistrement _site_transient_update_plugins. La structure de cet enregistrement est la suivante:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
stdClass {
	"last_checked"		=> 1323347150,
	"checked"		=> array(
		"akismet/akismet.php"			=> "2.5.3",
		"hello.php"				=> "1.6",
		'contact-form-7/wp-contact-form-7.php'	=> "3.0.1",
		'wordpress-seo/wp-seo.php'		=> "1.0.3",
		'wp-super-cache/wp-cache.php'		=>  "0.0.9",
		"wp-weather/wp-weather.php"		=> "0.3.11"
	),
	"response"	=> array(
		"wp-super-cache/wp-cache.php"	=> stdClass {
			"id"		=> 400,
			"slug"		=> "wp-wp-cache",
			"new_version"	=> "1.0",
			"url"		=> "http://wordpress.org/extend/plugins/wp-super-cache/",
			"package"	=> "http://downloads.wordpress.org/plugin/wp-super-cache.1.0.zip";
		}
	)
}

Donc, nous avons bien la date de la dernière vérification, la liste des extensions vérifiées, et la liste des extensions qui nécessitent une mise à jour (WP-Super-Cache dans notre cas).

Pour simuler la mise à jour localement, il suffit donc de modifier la valeur de l’enregistrement _site_transient_update_plugins, en y incluant les bons paramètres. Par exemple, nous pourrions créer une entrée supplémentaire dans response, avec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"response"	=> array(
		"wp-super-cache/wp-cache.php"	=> stdClass {
			"id"		=> 400,
			"slug"		=> "WP-Super-Cache",
			"new_version"	=> "1.0",
			"url"		=> "http://wordpress.org/extend/plugins/wp-super-cache/",
			"package"	=> "http://downloads.wordpress.org/plugin/wp-super-cache.1.0.zip";
		},
		"eg-series/eg-series.php"=> stdClass {
			"id"		=> 1000,
			"slug"		=> "EG-Series",
			"new_version"	=> "2.0.4",
			"url"		=> " http://localhost/wp330/extend/plugins/eg-series",
			"package"	=> " http://localhost/wp330/repository/eg-series.2.0.4.zip";
		}
	)
Etape 3: le code

Voici donc un exemple complet d’utilisation de cette astuce, sous forme de plugin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?php
/*
Plugin Name: EG-Update
Plugin URI:
Description: Simulate the update process
Version: 1.0.0
Author: Emmanuel GEORJON
Author URI: http://www.emmanuelgeorjon.com/
*/
 
if ( is_admin() ) {
 
	if (! class_exists('EG_Update')) {
 
		Class EG_Update {
 
			function load() {
				// Add action to create the menu Tools/EG-Update
				add_action('admin_menu', array(&$this, 'admin_menu'));
			}
 
			function admin_menu() {
				// Add the menu
				add_submenu_page( 'tools.php', 'EG-Update Plugin' , 'EG-Update', 'manage_options', 'egu_tools', array(&$this, 'update_plugin'));
			}
 
			// Update the option "update_plugins" to add the plugin
			function update_new_version($plugin, $new_version) {
 
				// Get the option
				$current = get_site_transient( 'update_plugins' );
				if (! isset($current->response)) $current->response = array();
 
				// Fill the new record
				$plugin_info 		  = new stdClass;
				$plugin_info->id 	  = 999 + rand(1, 500);
				$plugin_info->new_version = $new_version;
				$plugin_info->slug	  = $plugin;
				$plugin_info->url	  = 'http://localhost/wp330/my_repository/'.$plugin.'/';
				$plugin_info->package	  = 'http://localhost/wp330/my_repository/'.$plugin.'.'.$new_version.'.zip';
 
				// Save the option
				$current->response[$plugin] = $plugin_info;
				set_site_transient('update_plugins', $current );
 
			} // End of get_new_version
 
			// Page EG-Update: form to select the plugin and enter the new version
			function update_plugin() {
 
				if (isset($_POST['egu_plugin']) 		&&
					$_POST['egu_plugin'] != 'none' 		&&
					isset($_POST['egu_new_version']) 	&&
					$_POST['egu_new_version']!='')  $this->update_new_version($_POST['egu_plugin'], $_POST['egu_new_version']);
	?>
				<div class="wrap">
					<?php screen_icon(); ?>
					<h2><?php echo 'EG-Update Plugin'; ?></h2><br />
					<form action="" method="POST">
						Select the plugin to update:
						<select id="egu_plugin" name="egu_plugin">
							<option value="none"> </option>
	<?php
					// Get the list of installed plugins
					$plugins = get_plugins();
					// File the select html tag
					foreach ($plugins as $file => $p) {
							echo '<option value="'.$file.'">'.$p['Name'].'</option>';
					}
	?>
						</select>
						<br />Enter the new version:
						<input type="text" id="egu_new_version" name="egu_new_version" value="" />
						<?php  echo submit_button(); ?>
					</form>
				</div>
	<?php
			} // End of update_plugin
		} // End of Class
	} // End of if class_exists
 
	$eg_update = new EG_Update();
	$eg_update->load();
 
} // If is_admin
?>
  • En ligne 20, permet de spéficier la fonction qui sera utilisée pour la création du menu,
  • En ligne 25 est crée le menu « Outils / EG-Update » proprement dit,
  • La fonction qui débute en ligne 50, récupère la liste des plugins (ligne 66), boucle sur cette liste pour constuire la combo-box (lignes 68 à 70), et construit le formulaire complet. Elle permet également d’intercepter les informations saisies par l’utilisateur (lignes 52 à 55),
  • La fonction update_new_version qui commence en ligne 29, effectue la modification de l’option _site_transient_update_plugins, à partir des informations fournies par le formulaire.

Il ne vous reste plus qu’a déposer la nouvelle version de votre plugin, au bon endroit. Dans cet exemple, le fichier eg-series.2.0.5.zip sera déposé dans le répertoire /temp/dev/htdocs/wp330/repository.

En sélectionnant le plugin EG-Series dans le formulaire, ainsi qu’une version supérieure à la version actuelle, nous voyons bien apparaître les notifications de mise à jour, comme prévu.

Vous pouvez télécharger le code complet ICI:

Plugin EG-Update
Titre : Plugin EG-Update
Légende : Plugin EG-Update
Nom du fichier : eg-update.zip
Taille : 1 Ko

Pour qu’il fonctionne sur votre propre installation, il vous faudra éditer le plugin, pour y modifier notamment les chemins d’accès aux packages.

Conclusion

Pour un développeur, il est relativement simple de modifier le comportement du système de mise à jour, pour l’adapter à ses besoins, notamment:

  • Exclure les plugins en cours de développement, de la liste des plugins dont il faut chercher une mise à jour
  • Ou forcer la mise à jour d’un plugin, avec une nouvelle version se trouvant sur un serveur de développement