diff --git a/.gitattributes b/.gitattributes
index 1913eb1d4a889b764d9e977d645b782db218db6b..38bd4b0f0c6bb156cc3eedf30d1197b4bb9472da 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,6 +4,7 @@ config/ecran_securite.php -text
 config/remove.txt -text
 ecrire/action/activer_plugins.php -text
 ecrire/action/ajouter_lien.php -text
+ecrire/action/annuler_job.php -text
 ecrire/action/auth.php -text
 ecrire/action/charger_plugin.php -text
 ecrire/action/confirmer_email.php -text
@@ -16,6 +17,7 @@ ecrire/action/editer_liens.php -text
 ecrire/action/editer_objet.php -text
 ecrire/action/editer_rubrique.php -text
 ecrire/action/etre_webmestre.php -text
+ecrire/action/forcer_job.php -text
 ecrire/action/iconifier.php -text
 ecrire/action/index.php -text
 ecrire/action/instituer_article.php -text
@@ -25,10 +27,12 @@ ecrire/action/logout.php -text
 ecrire/action/menu_rubriques.php -text
 ecrire/action/preferer.php -text
 ecrire/action/purger.php -text
+ecrire/action/purger_queue.php -text
 ecrire/action/redirect.php -text
 ecrire/action/referencer_traduction.php -text
 ecrire/action/reorganiser.php -text
 ecrire/action/session.php -text
+ecrire/action/super_cron.php -text
 ecrire/action/supprimer_lien.php -text
 ecrire/action/supprimer_rubrique.php -text
 ecrire/action/tester.php -text
@@ -139,6 +143,7 @@ ecrire/inc/prepare_recherche.php -text
 ecrire/inc/presentation_mini.php -text
 ecrire/inc/presenter_enfants.php -text
 ecrire/inc/puce_statut.php -text
+ecrire/inc/queue.php -text
 ecrire/inc/recherche_to_array.php -text
 ecrire/inc/rechercher.php -text
 ecrire/inc/securiser_action.php -text
@@ -701,6 +706,7 @@ prive/modeles/formulaire.html -text
 prive/modeles/image.html -text
 prive/modeles/img.html -text
 prive/modeles/mail_inscription.html -text
+prive/modeles/object_jobs_list.html -text
 prive/modeles/pagination.html -text
 prive/modeles/pagination_page.html -text
 prive/modeles/pagination_page_precedent_suivant.html -text
@@ -766,6 +772,7 @@ prive/squelettes/contenu/configurer_langue.html -text
 prive/squelettes/contenu/configurer_multilang.html -text
 prive/squelettes/contenu/configurer_preferences.html -text
 prive/squelettes/contenu/infos_perso.html -text
+prive/squelettes/contenu/job_queue.html -text
 prive/squelettes/contenu/navigation.html -text
 prive/squelettes/contenu/plan.html -text
 prive/squelettes/contenu/recherche.html -text
@@ -975,6 +982,9 @@ prive/themes/spip/images/puce-proposer-8.png -text
 prive/themes/spip/images/puce-publier-8.png -text
 prive/themes/spip/images/puce-refuser-8.png -text
 prive/themes/spip/images/puce-supprimer-8.png -text
+prive/themes/spip/images/queue-process-16.png -text
+prive/themes/spip/images/queue-process-24.png -text
+prive/themes/spip/images/queue-process-32.png -text
 prive/themes/spip/images/racine-16.png -text
 prive/themes/spip/images/racine-24.png -text
 prive/themes/spip/images/reaction-48.png -text
diff --git a/ecrire/action/annuler_job.php b/ecrire/action/annuler_job.php
new file mode 100644
index 0000000000000000000000000000000000000000..87e701f7a0dfa0d8bd51921ee120e4501c973e54
--- /dev/null
+++ b/ecrire/action/annuler_job.php
@@ -0,0 +1,30 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2011                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Annuler un travail
+ * @return void
+ */
+function action_annuler_job_dist(){
+	$securiser_action = charger_fonction('securiser_action','inc');
+	$id_job = $securiser_action();
+
+	if ($id_job = intval($id_job)
+		AND autoriser('annuler','job',$id_job)
+	){
+		job_queue_remove($id_job);
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/ecrire/action/forcer_job.php b/ecrire/action/forcer_job.php
new file mode 100644
index 0000000000000000000000000000000000000000..82c1def47730c9c685dac1afc512190ed40bf86b
--- /dev/null
+++ b/ecrire/action/forcer_job.php
@@ -0,0 +1,33 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2011                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Executer un travaille immediatement
+ * @return void
+ */
+function action_forcer_job_dist(){
+	$securiser_action = charger_fonction('securiser_action','inc');
+	$id_job = $securiser_action();
+
+	if ($id_job = intval($id_job)
+		AND autoriser('forcer','job',$id_job)
+	){
+		include_spip('inc/queue');
+		include_spip('inc/genie');
+		queue_schedule(array($id_job));
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/ecrire/action/purger_queue.php b/ecrire/action/purger_queue.php
new file mode 100644
index 0000000000000000000000000000000000000000..a9c7caff4798640bd7f987a28f2f02091bd3371a
--- /dev/null
+++ b/ecrire/action/purger_queue.php
@@ -0,0 +1,30 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2011                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Purger la liste des travaux en attente
+ * @return void
+ */
+function action_purger_queue_dist(){
+	$securiser_action = charger_fonction('securiser_action','inc');
+	$securiser_action();
+
+	if (autoriser('purger','queue')){
+		include_spip('inc/queue');
+		queue_purger();
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/ecrire/action/super_cron.php b/ecrire/action/super_cron.php
new file mode 100644
index 0000000000000000000000000000000000000000..de008fed276557f3e433531b45f4362a733d80f9
--- /dev/null
+++ b/ecrire/action/super_cron.php
@@ -0,0 +1,50 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2011                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Url pour lancer le cron de manière asynchrone si le serveur
+ * le permet
+ *
+ * On se base sur le même code que celui du pipeline affichage final
+ *
+ * Cette fonction est utile pour être appelée depuis un cron UNIX par exemple
+ * car elle retourne tout de suite
+ *
+ * Exemple de tache cron Unix pour un appel toutes les minutes :
+ * "* * * * * curl  http://www.mondomaine.tld/spip.php?action=super_cron"
+ */
+function action_super_cron_dist(){
+	// Si fsockopen est possible, on lance le cron via un socket
+	// en asynchrone
+	if(function_exists('fsockopen')){
+		$url = generer_url_action('cron');
+		$parts=parse_url($url);
+		$fp = fsockopen($parts['host'],
+	        isset($parts['port'])?$parts['port']:80,
+	        $errno, $errstr, 30);
+		if ($fp) {
+	    	$out = "GET ".$parts['path']."?".$parts['query']." HTTP/1.1\r\n";
+    		$out.= "Host: ".$parts['host']."\r\n";
+    		$out.= "Connection: Close\r\n\r\n";
+			fwrite($fp, $out);
+			fclose($fp);
+			return;
+		}
+	}
+	// ici lancer le cron par un CURL asynchrone si CURL est présent
+	// TBD
+
+	return;
+}
+?>
\ No newline at end of file
diff --git a/ecrire/base/auxiliaires.php b/ecrire/base/auxiliaires.php
index 5d15bb7aa754f3b76711097820c224eb560d5bf3..e26d4318fe6fadd6a6c3bf08ce987e2459fb1b76 100644
--- a/ecrire/base/auxiliaires.php
+++ b/ecrire/base/auxiliaires.php
@@ -46,6 +46,16 @@ $spip_meta = array(
 $spip_meta_key = array(
 		"PRIMARY KEY"	=> "nom");
 
+$spip_jobs_liens = array(
+	"id_job"	=> "bigint(21) DEFAULT '0' NOT NULL",
+	"id_objet"	=> "bigint(21) DEFAULT '0' NOT NULL",
+	"objet"	=> "VARCHAR (25) DEFAULT '' NOT NULL",
+);
+
+$spip_documents_liens_key = array(
+		"PRIMARY KEY"		=> "id_job,id_objet,objet",
+		"KEY id_job"	=> "id_job");
+
 $tables_auxiliaires['spip_auteurs_liens'] = array(
 	'field' => &$spip_auteurs_liens,
 	'key' => &$spip_auteurs_liens_key);
@@ -56,7 +66,10 @@ $tables_auxiliaires['spip_meta'] = array(
 $tables_auxiliaires['spip_resultats'] = array(
 	'field' => &$spip_resultats,
 	'key' => &$spip_resultats_key);
-	
+$tables_auxiliaires['spip_jobs_liens'] = array(
+	'field' => &$spip_jobs_liens,
+	'key' => &$spip_documents_liens_key);
+
 	$tables_auxiliaires = pipeline('declarer_tables_auxiliaires',$tables_auxiliaires);
 }
 
diff --git a/ecrire/base/serial.php b/ecrire/base/serial.php
index 12eb0f803a85b3b44f7f0cdfb7d77313ac7cb462..ff167451030eefc38f5fded0e88132676ed1d31f 100644
--- a/ecrire/base/serial.php
+++ b/ecrire/base/serial.php
@@ -115,6 +115,24 @@ $spip_rubriques_key = array(
 );
 
 
+$spip_jobs = array(
+	"id_job" 	=> "bigint(21) NOT NULL",
+	"descriptif"	=> "text DEFAULT '' NOT NULL",
+	"fonction" 	=> "varchar(255) NOT NULL", //nom de la fonction
+	"args"=> "longblob DEFAULT '' NOT NULL", // arguments
+	"md5args"=> "char(32) NOT NULL default ''", // signature des arguments
+	"inclure" => "varchar(255) NOT NULL", // fichier a inclure ou path/ pour charger_fonction
+	"priorite" 	=> "smallint(6) NOT NULL default 0",
+	"date" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL", // date au plus tot
+	"status" => "tinyint NOT NULL default 1",
+	);
+
+$spip_jobs_key = array(
+	"PRIMARY KEY" 	=> "id_job",
+	"KEY date" => "date",
+	"KEY status" => "status",
+);
+
 /// Attention: mes_fonctions peut avoir deja defini cette variable
 /// il faut donc rajouter, mais pas reinitialiser
 
@@ -124,6 +142,8 @@ $tables_principales['spip_auteurs']  =
 	array('field' => &$spip_auteurs, 'key' => &$spip_auteurs_key,'join' => &$spip_auteurs_join);
 $tables_principales['spip_rubriques'] =
 	array('field' => &$spip_rubriques, 'key' => &$spip_rubriques_key);
+$tables_principales['spip_jobs'] =
+	array('field' => &$spip_jobs, 'key' => &$spip_jobs_key);
 
 	$tables_principales = pipeline('declarer_tables_principales',$tables_principales);
 }
diff --git a/ecrire/core.xml b/ecrire/core.xml
index 2656751bf49dcc0416b9a2042ee4b16d7bdfe4fb..28d70f5e749249be86866e39c929c4be48c398d1 100644
--- a/ecrire/core.xml
+++ b/ecrire/core.xml
@@ -78,7 +78,11 @@
 		<titre>titre_admin_tech</titre>
 		<url>admin_tech</url>
 	</bouton>
-	
+	<bouton id="job_queue" parent='bando_administration'>
+		<icone>images/queue-process-16.png</icone>
+		<titre>queue_titre</titre>
+	</bouton>
+
 	
 	
 	<bouton id="bando_configuration">
@@ -185,6 +189,7 @@
 	</pipeline>
 	<pipeline><nom>affichage_final</nom><action>f_tidy</action></pipeline>
 	<pipeline><nom>affichage_final</nom><action>f_admin</action></pipeline>
+	<pipeline><nom>affichage_final</nom><action>f_queue</action></pipeline>
 	<pipeline><nom>affichage_final_prive</nom>
 		<action>affichage_final_prive_title_auto</action>
 		<inclure>inc/pipelines_ecrire.php</inclure>
@@ -200,7 +205,7 @@
 	<pipeline><nom>affiche_auteurs_interventions</nom><action></action></pipeline>
 	<pipeline><nom>affiche_droite</nom><action></action></pipeline>
 	<pipeline><nom>affiche_gauche</nom><action></action></pipeline>
-	<pipeline><nom>affiche_milieu</nom><action></action></pipeline>
+	<pipeline><nom>affiche_milieu</nom><action>f_queue_affiche_milieu</action></pipeline>
 	<pipeline><nom>affiche_enfants</nom><action></action></pipeline>
 	<pipeline><nom>affiche_hierarchie</nom><action></action></pipeline>
 	<pipeline><nom>affiche_formulaire_login</nom>
diff --git a/ecrire/inc/autoriser.php b/ecrire/inc/autoriser.php
index 790e34dde0a36574fc9fd517f1512c29dd6b27d8..a1dcf5d093998e790f2690b658313b4705431a44 100644
--- a/ecrire/inc/autoriser.php
+++ b/ecrire/inc/autoriser.php
@@ -581,4 +581,12 @@ function autoriser_synchro_bouton_dist($faire, $type, $id, $qui, $opts){
 	return $qui['statut']=='0minirezo';
 }
 
-?>
+/**
+ * Autoriser la purge de la queue : il faut etre webmestre
+ * @return mixed
+ */
+function autoriser_queue_purger_dist(){
+	return autoriser('webmestre');
+}
+
+?>
\ No newline at end of file
diff --git a/ecrire/inc/genie.php b/ecrire/inc/genie.php
index bb589eb2f92885b8baf54186b45e14f977dc9abb..90c0dd55f2850f14a45474442469efc4cba9737c 100644
--- a/ecrire/inc/genie.php
+++ b/ecrire/inc/genie.php
@@ -52,43 +52,21 @@ if (!defined('_ECRIRE_INC_VERSION')) return;
 
 // http://doc.spip.org/@inc_genie_dist
 function inc_genie_dist($taches = array()) {
-
-	if (!$taches)
-		$taches = taches_generales();
-
-	// Quelle est la tache la plus urgente ?
-	$tache = '';
-	$tmin = $t = time();
-	foreach ($taches as $nom => $periode) {
-		$celock = _DIR_TMP . $nom . '.lock';
-		$date_lock = @filemtime($celock);
-		if ($date_lock + $periode < $tmin) {
-			$tmin = $date_lock + $periode;
-			$tache = $nom;
-			$lock = $celock;
-			$last = $date_lock;
-		}
-	// debug : si la date du fichier est superieure a l'heure actuelle,
-	// c'est que les serveurs Http et de fichiers sont desynchro.
-	// Ca peut mettre en peril les taches cron : signaler dans le log
-	// (On laisse toutefois flotter sur une heure, pas la peine de s'exciter
-	// pour si peu)
-		else if ($date_lock > $t + 3600)
-			spip_log("Erreur de date du fichier $lock : $date_lock > $t !");
-	}
-	if ($tache) {
-		spip_timer('tache');
-		spip_log('cron: debut '.$tache, 'genie');
-		touch($lock);
-		$cron = charger_fonction($tache, 'genie');
-		$retour = $cron($last);
-		// si la tache a eu un effet : log
-		if ($retour) {
-			spip_log("cron: $tache (" . spip_timer('tache') . ") $retour", 'genie');
-			if ($retour < 0)
-				@touch($lock, 0 - $retour);
-		}
-	}
+	include_spip('inc/queue');
+
+	if (_request('exec')=='job_queue')
+		return;
+
+	$force_jobs = array();
+	// l'ancienne facon de lancer une tache cron immediatement
+	// etait de la passer en parametre a ing_genie_dist
+	// on reroute en ajoutant simplement le job a la queue, ASAP
+	foreach($taches as $function=>$period)
+		$force_jobs[] = queue_add_job($function, _L("Tache CRON $function (ASAP)"), array(time()-abs($period)), "genie/");
+	
+	// et on passe la main a la gestion de la queue !
+	// en forcant eventuellement les jobs ajoute a l'instant
+	queue_schedule(count($force_jobs)?$force_jobs:null);
 }
 
 //
@@ -102,6 +80,10 @@ function inc_genie_dist($taches = array()) {
 // http://doc.spip.org/@taches_generales
 function taches_generales($taches_generales = array()) {
 
+	// verifier que toutes les taches cron sont planifiees
+	// c'est une tache cron !
+	$taches_generales['queue_watch'] = 3600*24;
+
 	// MAJ des rubriques publiques (cas de la publication post-datee)
 	// est fait au coup par coup a present
 	//	$taches_generales['rubriques'] = 3600;
@@ -109,8 +91,9 @@ function taches_generales($taches_generales = array()) {
 	// Optimisation de la base
 	$taches_generales['optimiser'] = 3600*48;
 
-	// cache (chaque 20 minutes => 1/16eme du repertoire cache)
-	$taches_generales['invalideur'] = 1200;
+	// cache (chaque 10 minutes => 1/16eme du repertoire cache,
+	// soit toutes les 2h40 sur le meme rep)
+	$taches_generales['invalideur'] = 600;
 
 	// nouveautes
 	if ($GLOBALS['meta']['adresse_neuf'] AND $GLOBALS['meta']['jours_neuf']
@@ -141,4 +124,52 @@ function genie_invalideur_dist($t) {
 		return (0 - $t);
 	return 1;
 }
+
+/**
+ * Une tache periodique pour surveiller les taches crons et les relancer si besoin
+ * quand ce cron s'execute, il n'est plus dans la queue, donc il se replanifie
+ * lui meme, avec last=time()
+ * avec une dose d'aleatoire pour ne pas planifier toutes les taches au meme moment
+ *
+ * @return int
+ */
+function genie_queue_watch_dist(){
+	$taches = taches_generales();
+	foreach($taches as $tache=>$periode){
+		queue_genie_replan_job($tache,$periode,time()-round(rand(1,$periode)));
+	}
+	return 1;
+}
+
+/**
+ * Replanifier une tache periodique
+ *
+ * @param string $function
+ *   nom de la fonction a appeler
+ * @param int $period
+ *   periodicite en secondes
+ * @param int $last
+ *   date du dernier appel (timestamp)
+ * @param int $time
+ *   date de replanification
+ * @param int $priority
+ *   priorite
+ * @return void
+ */
+function queue_genie_replan_job($function,$period,$last=null,$time=0, $priority=0){
+		if (!$time){
+			if (!is_null($last))
+				$time = $last+$period;
+			else
+				$time=time();
+		}
+		if (is_null($last))
+			$last = $time-$period;
+		spip_log("replan_job $function $period $last $time $priority",'queue');
+		include_spip('inc/queue');
+		// on replanifie un job cron
+		// uniquement si il n'y en a pas deja un avec le meme nom
+		// independament de l'argument
+		queue_add_job($function, _L("Tache CRON $function (toutes les $period s)"), array($last), "genie/", 'function_only', $time, $priority);
+}
 ?>
diff --git a/ecrire/inc/pipelines.php b/ecrire/inc/pipelines.php
index f5c6801bb78afe986d9d90cdafcb56ddae12e18f..36a428adba44f0766ce17598e5457fe5f1a204a3 100644
--- a/ecrire/inc/pipelines.php
+++ b/ecrire/inc/pipelines.php
@@ -111,4 +111,31 @@ function f_recuperer_fond($flux) {
 	include_spip('inc/pipelines_ecrire');
 	return f_afficher_blocs_ecrire($flux);
 }
-?>
+
+// gerer le lancement du cron
+// si des taches sont en attentes
+function f_queue(&$texte){
+
+	// eviter une inclusion si rien a faire
+	if (queue_sleep_time_to_next_job() OR defined('_DEBUG_BLOCK_QUEUE')){
+		return $texte;
+	}
+
+	include_spip('inc/queue');
+	$code = queue_affichage_cron();
+
+	// si rien a afficher
+	// ou si on est pas dans une page html, on ne sait rien faire de mieux
+	if (!$code OR !$GLOBALS['html'])
+		return $texte;
+
+	// inserer avant le </body> fermant si on peut, a la fin de la page sinon
+	if (($p=strpos($texte,'</body>'))!==FALSE)
+		$texte = substr($texte,0,$p).$code.substr($texte,$p);
+	else
+		$texte .= $code;
+
+	return $texte;
+}
+
+?>
\ No newline at end of file
diff --git a/ecrire/inc/pipelines_ecrire.php b/ecrire/inc/pipelines_ecrire.php
index 9782ad17f7737d7ec8bc97980b78d848f426f3d3..586151a06fc8739de6e7d74e3a812f2b60fab845 100644
--- a/ecrire/inc/pipelines_ecrire.php
+++ b/ecrire/inc/pipelines_ecrire.php
@@ -246,6 +246,26 @@ function f_afficher_blocs_ecrire($flux) {
 	return $flux;
 }
 
+/**
+ * Afficher les taches en attente liees a un objet
+ * @param string $flux
+ * @return string
+ */
+function f_queue_affiche_milieu($flux){
+	$args = $flux['args'];
+	$res = "";
+	foreach($args as $key=>$arg){
+		if (preg_match(",^id_,",$key)){
+			$objet = preg_replace(',^id_,', '', $key);
+			$res .= recuperer_fond('modeles/object_jobs_list',array('id_objet'=>$arg,'objet'=>$objet),array('ajax'=>true));
+		}
+	}
+	if ($res)
+		$flux['data'] = $res . $flux['data'];
+
+	return $flux;
+}
+
 /**
  * Trouver l'objet qui correspond
  * a l'exec de l'espace prive passe en argument
diff --git a/ecrire/inc/presentation_mini.php b/ecrire/inc/presentation_mini.php
index eb3e33d5bd754cb2b5539f156d96e3b8e5b3a75c..bb6031be8e1f103af0640498cdc3a0771d4b6831 100644
--- a/ecrire/inc/presentation_mini.php
+++ b/ecrire/inc/presentation_mini.php
@@ -80,14 +80,16 @@ function liste_objets_bloques($exec,$contexte=array(),$auteur=null){
 // Elle comporte une image invisible declenchant une tache de fond
 // http://doc.spip.org/@fin_page
 function fin_page(){
+	include_spip('inc/pipelines');
 	// avec &var_profile=1 on a le tableau de mesures SQL
 	$debug = ((_request('exec') !== 'valider_xml')  AND ((_request('var_mode') == 'debug') OR $GLOBALS['tableau_des_temps'] AND isset($_COOKIE['spip_admin'])));
-	return '<div id="pied">'
+	$t = '<div id="pied">'
 	. recuperer_fond('prive/squelettes/inclure/pied')
 	. "</div>"
 	. "</div></div>" // cf. div#page et div.largeur ouvertes dans conmmencer_page()
 	. ($debug?erreur_squelette():'')
 	. "</body></html>\n";
+	return f_queue($t);
 }
 
 function html_tests_js(){
@@ -99,9 +101,6 @@ function html_tests_js(){
 		        . "' width='1' height='1' alt='' /></div></noscript>\n");
 	}
 	return $GLOBALS['rejoue_session']
-	. '<div style="background-image: url(\''
-	. generer_url_action('cron')
-	. '\');"></div>'
 	. (defined('_TESTER_NOSCRIPT') ? _TESTER_NOSCRIPT : '');
 }
 
diff --git a/ecrire/inc/queue.php b/ecrire/inc/queue.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ba5f6ded81492da4b8a7f102cc0773bec6de579
--- /dev/null
+++ b/ecrire/inc/queue.php
@@ -0,0 +1,520 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2009                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+define('_JQ_SCHEDULED',1);
+define('_JQ_PENDING',0);
+#define('_JQ_MAX_JOBS_EXECUTE',200); // pour personaliser le nombre de jobs traitables a chaque hit
+#define('_JQ_MAX_JOBS_TIME_TO_EXECUTE',15); // pour personaliser le temps d'excution dispo a chaque hit
+#define('_JQ_NB_JOBS_OVERFLOW',10000); // nombre de jobs a partir duquel on force le traitement en fin de hit pour purger
+
+/**
+ * Ajouter une tache a la file
+ * Les taches sont ensuites executees par date programmee croissant/priorite decroissante
+ *
+ * @param $function
+ *   The function name to call.
+ * @param $description
+ *   A human-readable description of the queued job.
+ * @param $arguments
+ *   Optional array of arguments to pass to the function.
+ * @param $file
+ *   Optional file path which needs to be included for $fucntion.
+ * @param $no_duplicate
+ *   If TRUE, do not add the job to the queue if one with the same function and
+ *   arguments already exists.
+ *	 If 'function_only' test of existence is only on function name (for cron job)
+ * @param $time
+ *		time for starting the job. If 0, job will start as soon as possible
+ * @param $priority
+ *		-10 (low priority) to +10 (high priority), 0 is the default
+ * @return int
+ *	id of job
+ */
+function queue_add_job($function, $description, $arguments = array(), $file = '', $no_duplicate = false, $time=0, $priority=0){
+	include_spip('base/abstract_sql');
+
+	// cas pourri de ecrire/action/editer_site avec l'option reload=oui
+	if (defined('_GENIE_SYNDIC_NOW'))
+		$arguments['id_syndic'] = _GENIE_SYNDIC_NOW;
+
+	// serialiser les arguments
+	$arguments = serialize($arguments);
+	$md5args = md5($arguments);
+
+	// si option ne pas dupliquer, regarder si la fonction existe deja
+	// avec les memes args et file
+	if (
+			$no_duplicate
+		AND
+			sql_countsel('spip_jobs',
+				'status='.intval(_JQ_SCHEDULED).' AND fonction='.sql_quote($function)
+				.(($no_duplicate==='function_only')?'':
+				 ' AND md5args='.sql_quote($md5args).' AND inclure='.sql_quote($file)))
+		)
+		return false;
+
+	// si pas de date programee, des que possible
+	if (!$time)
+		$time = time();
+	$date = date('Y-m-d H:i:s',$time);
+
+	$id_job = sql_insertq('spip_jobs',array(
+			'fonction'=>$function,
+			'descriptif'=>$description,
+			'args'=>$arguments,
+			'md5args'=>$md5args,
+			'inclure'=>$file,
+			'priorite'=>max(-10,min(10,intval($priority))),
+			'date'=>$date,
+			'status'=>_JQ_SCHEDULED,
+		));
+
+	// une option de debug pour verifier que les arguments en base sont bons
+	// ie cas d'un char non acceptables sur certains type de champs
+	// qui coupe la valeur
+	if (defined('_JQ_INSERT_CHECK_ARGS') AND $id_job) {
+		$args = sql_getfetsel('args', 'spip_jobs', 'id_job='.intval($id_job));
+		if ($args!==$arguments) {
+			spip_log('arguments job errones / longueur '.strlen($args)." vs ".strlen($arguments).' / valeur : '.var_export($arguments,true),'queue');
+		}
+	}
+
+	if ($id_job){
+		queue_update_next_job_time($time);
+	}
+
+	return $id_job;
+
+}
+
+/**
+ * Purger la file de tache
+ * et reprgrammer les taches periodiques
+ * 
+ * @return void
+ */
+function queue_purger(){
+	include_spip('base/abstract_sql');
+	sql_delete('spip_jobs');
+  sql_delete("spip_jobs_liens","id_job NOT IN (".sql_get_select("id_job","spip_jobs").")");
+  include_spip('inc/genie');
+  genie_queue_watch_dist();
+}
+
+/**
+ * Retirer une tache de la file d'attente
+ * @param int $id_job
+ *  id de la tache a retirer
+ * @return bool
+ */
+function queue_remove_job($id_job){
+	include_spip('base/abstract_sql');
+
+	if ($row = sql_fetsel('fonction,inclure,date','spip_jobs','id_job='.intval($id_job))
+	 AND $res = sql_delete('spip_jobs','id_job='.intval($id_job))){
+		queue_unlink_job($id_job);
+		// est-ce une tache cron qu'il faut relancer ?
+		if ($periode = queue_is_cron_job($row['fonction'],$row['inclure'])){
+			// relancer avec les nouveaux arguments de temps
+			include_spip('inc/genie');
+			// relancer avec la periode prevue
+			queue_genie_replan_job($row['fonction'],$periode,strtotime($row['date']));
+		}
+		queue_update_next_job_time();
+	}
+	return $res;
+}
+
+/**
+ * Associer une tache avec un objet
+ *
+ * @param int $id_job
+ *	id of job to link
+ * @param array $objets
+ *  can be a simple array('objet'=>'article','id_objet'=>23)
+ *  or an array of simple array to link multiples objet in one time
+ */
+function queue_link_job($id_job,$objets){
+	include_spip('base/abstract_sql');
+
+	if (is_array($objets) AND count($objets)){
+		if (is_array(reset($objets))){
+			foreach($objets as $k=>$o){
+				$objets[$k]['id_job'] = $id_job;
+			}
+			sql_insertq_multi('spip_jobs_liens',$objets);
+		}
+		else
+			sql_insertq('spip_jobs_liens',array_merge(array('id_job'=>$id_job),$objets));
+	}
+}
+
+/**
+ * Dissocier une tache d'un objet
+ *
+ * @param int $id_job
+ *	id of job to unlink ibject with
+ * @return int/bool
+ *	result of sql_delete
+ */
+function queue_unlink_job($id_job){
+	return sql_delete("spip_jobs_liens","id_job=".intval($id_job));
+}
+
+/**
+ * Lancer une tache decrite par sa ligne SQL
+ * @param array $row
+ *	describe the job, with field of table spip_jobs
+ * @return mixed
+ *	return the result of job
+ */
+function queue_start_job($row){
+
+	// deserialiser les arguments
+	$args = unserialize($row['args']);
+	if ($args===false){
+		spip_log('arguments job errones '.var_export($row,true),'queue');
+		$args = array();
+	}
+
+	$fonction = $row['fonction'];
+	if (strlen($inclure = trim($row['inclure']))){
+		if (substr($inclure,-1)=='/'){ // c'est un chemin pour charger_fonction
+			$f = charger_fonction($fonction,rtrim($inclure,'/'),false);
+			if ($f)
+				$fonction = $f;
+		}
+		else
+			include_spip($inclure);
+	}
+
+	if (!function_exists($fonction)){
+		spip_log("fonction $fonction ($inclure) inexistante ".var_export($row,true),'queue');
+		return false;
+	}
+
+	spip_log("queue [".$row['id_job']."]: $fonction() start", 'queue');
+	switch (count($args)) {
+		case 0:	$res = $fonction(); break;
+		case 1:	$res = $fonction($args[0]); break;
+		case 2:	$res = $fonction($args[0],$args[1]); break;
+		case 3:	$res = $fonction($args[0],$args[1], $args[2]); break;
+		case 4:	$res = $fonction($args[0],$args[1], $args[2], $args[3]); break;
+		case 5:	$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4]); break;
+		case 6:	$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4], $args[5]); break;
+		case 7:	$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break;
+		case 8:	$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break;
+		case 9:	$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break;
+		case 10:$res = $fonction($args[0],$args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8], $args[9]); break;
+		default:
+			# plus lent mais completement generique
+			$res = call_user_func_array($fonction, $args);
+	}
+	spip_log("queue [".$row['id_job']."]: $fonction() end", 'queue');
+	return $res;
+
+}
+
+/**
+ * Scheduler :
+ * Prend une par une les taches en attente
+ * et les lance, dans la limite d'un temps disponible total
+ * et d'un nombre maxi de taches
+ *
+ * La date de la prochaine tache a executer est mise a jour
+ * apres chaque chaque tache finie
+ * afin de relancer le scheduler uniquement quand c'est necessaire
+ *
+ * @param array $force_jobs
+ *   list of id_job to execute when provided
+ */
+function queue_schedule($force_jobs = null){
+	$time = time();
+	if (defined('_DEBUG_BLOCK_QUEUE')) {
+		spip_log("_DEBUG_BLOCK_QUEUE : schedule stop",'jq'._LOG_DEBUG);
+		return;
+	}
+
+	// rien a faire si le prochain job est encore dans le futur
+	if (queue_sleep_time_to_next_job() AND (!$force_jobs OR !count($force_jobs))){
+		spip_log("queue_sleep_time_to_next_job",'jq'._LOG_DEBUG);
+		return;
+	}
+
+	include_spip('base/abstract_sql');
+
+	if (!defined('_JQ_MAX_JOBS_TIME_TO_EXECUTE')){
+		$max_time = ini_get('max_execution_time')/2;
+		// valeur conservatrice si on a pas reussi a lire le max_execution_time
+		if (!$max_time) $max_time=5;
+		define('_JQ_MAX_JOBS_TIME_TO_EXECUTE',min($max_time,15)); // une valeur maxi en temps.
+	}
+	$end_time = $time + _JQ_MAX_JOBS_TIME_TO_EXECUTE;
+
+	spip_log("JQ schedule $time / $end_time",'jq'._LOG_DEBUG);
+
+	if (!defined('_JQ_MAX_JOBS_EXECUTE'))
+		define('_JQ_MAX_JOBS_EXECUTE',200);
+	$nbj=0;
+	// attraper les jobs
+	// dont la date est passee (echus en attente),
+	// par odre :
+	//	- de priorite
+	//	- de date
+	// lorsqu'un job cron n'a pas fini, sa priorite est descendue
+	// pour qu'il ne bloque pas les autres jobs en attente
+	if (is_array($force_jobs) AND count($force_jobs))
+		$cond = "status=".intval(_JQ_SCHEDULED)." AND ".sql_in("id_job", $force_jobs);
+	else {
+		$now = date('Y-m-d H:i:s',$time);
+		$cond = "status=".intval(_JQ_SCHEDULED)." AND date<".sql_quote($now);
+	}
+
+	register_shutdown_function('queue_error_handler'); // recuperer les erreurs auant que possible
+	$res = sql_allfetsel('*','spip_jobs',$cond,'','priorite DESC,date','0,'.(_JQ_MAX_JOBS_EXECUTE+1));
+	do {
+		if ($row = array_shift($res)){
+			$nbj++;
+			// il faut un verrou, a base de sql_delete
+			if (sql_delete('spip_jobs',"id_job=".intval($row['id_job'])." AND status=".intval(_JQ_SCHEDULED))){
+				#spip_log("JQ schedule job ".$nbj." OK",'jq');
+				// on reinsert dans la base aussitot avec un status=_JQ_PENDING
+				$row['status'] = _JQ_PENDING;
+				$row['date'] = $time;
+				sql_insertq('spip_jobs', $row);
+
+				// on a la main sur le job :
+				// l'executer
+				$result = queue_start_job($row);
+
+				$time = time();
+				queue_close_job($row, $time, $result);
+			}
+		}
+		spip_log("JQ schedule job end time ".$time,'jq'._LOG_DEBUG);
+	} while ($nbj<_JQ_MAX_JOBS_EXECUTE AND $row AND $time<$end_time);
+	spip_log("JQ schedule end time ".time(),'jq'._LOG_DEBUG);
+
+	if ($row = array_shift($res)){
+		queue_update_next_job_time(0); // on sait qu'il y a encore des jobs a lancer ASAP
+		spip_log("JQ encore !",'jq'._LOG_DEBUG);
+	}
+	else
+		queue_update_next_job_time();
+
+}
+
+/**
+ * Terminer un job au status _JQ_PENDING :
+ *  - le reprogrammer si c'est un cron
+ *  - supprimer ses liens
+ *  - le detruire en dernier
+ *
+ * @param array $row
+ * @param int $time
+ * @param int $result
+ */
+function queue_close_job(&$row,$time,$result=0){
+	// est-ce une tache cron qu'il faut relancer ?
+	if ($periode = queue_is_cron_job($row['fonction'],$row['inclure'])){
+		// relancer avec les nouveaux arguments de temps
+		include_spip('inc/genie');
+		if ($result<0)
+			// relancer tout de suite, mais en baissant la priorite
+			queue_genie_replan_job($row['fonction'],$periode,0-$result/*last*/,0/*ASAP*/,$row['priorite']-1);
+		else
+			// relancer avec la periode prevue
+			queue_genie_replan_job($row['fonction'],$periode,$time);
+	}
+	// purger ses liens eventuels avec des objets
+	sql_delete("spip_jobs_liens","id_job=".intval($row['id_job']));
+	// supprimer le job fini
+	sql_delete('spip_jobs','id_job='.intval($row['id_job']));
+}
+
+/**
+ * Recuperer des erreurs auant que possible
+ * en terminant la gestion de la queue
+ */
+function queue_error_handler(){
+	// se remettre dans le bon dossier, car Apache le change parfois (toujours?)
+	chdir(_ROOT_CWD);
+
+	queue_update_next_job_time();
+}
+
+
+/**
+ * Tester si une tache etait une tache periodique a reprogrammer
+ *
+ * @param <type> $function
+ * @param <type> $inclure
+ * @return <type>
+ */
+function queue_is_cron_job($function,$inclure){
+	static $taches = null;
+	if (strncmp($inclure,'genie/',6)==0){
+		if (is_null($taches)){
+			include_spip('inc/genie');
+			$taches = taches_generales();
+		}
+		if (isset($taches[$function]))
+			return $taches[$function];
+	}
+	return false;
+}
+
+/**
+ * Mettre a jour la date du prochain job a lancer
+ * Si une date est fournie (au format time unix)
+ * on fait simplement un min entre la date deja connue et celle fournie
+ * (cas de l'ajout simple
+ * ou cas $next_time=0 car l'on sait qu'il faut revenir ASAP)
+ *
+ * @param int $next_time
+ *	temps de la tache ajoutee ou 0 pour ASAP
+ */
+function queue_update_next_job_time($next_time=null){
+	static $nb_jobs_scheduled = null;
+	static $deja_la = false;
+	// prendre le min des $next_time que l'on voit passer ici, en cas de reentrance
+	static $next = null;
+	if (!is_null($next_time)){
+		if (is_null($next) OR $next>$next_time)
+			$next = $next_time;
+	}
+	// queue_close_job peut etre reentrant ici
+	if ($deja_la) return;
+	$deja_la = true;
+
+	include_spip('base/abstract_sql');
+	$time = time();
+
+	// traiter les jobs morts au combat (_JQ_PENDING depuis plus de 180s)
+	// pour cause de timeout ou autre erreur fatale
+	$res = sql_allfetsel("*","spip_jobs","status=".intval(_JQ_PENDING)." AND date<".sql_quote(date('Y-m-d H:i:s',$time-180)));
+	if (is_array($res)) {
+		foreach ($res as $row)
+			queue_close_job($row,$time);
+	}
+
+	// chercher la date du prochain job si pas connu
+	if (is_null($next) OR is_null(queue_sleep_time_to_next_job())){
+		$date = sql_getfetsel('date','spip_jobs',"status=".intval(_JQ_SCHEDULED),'','date','0,1');
+		$next = strtotime($date);
+	}
+	else {
+		if ($next){
+			if (is_null($nb_jobs_scheduled))
+				$nb_jobs_scheduled = sql_countsel('spip_jobs',"status=".intval(_JQ_SCHEDULED)." AND date<".sql_quote(date('Y-m-d H:i:s',$time)));
+			elseif ($next<=$time)
+				$nb_jobs_scheduled++;
+			// si trop de jobs en attente, on force la purge en fin de hit
+			// pour assurer le coup
+			if ($nb_jobs_scheduled>defined('_JQ_NB_JOBS_OVERFLOW')?_JQ_NB_JOBS_OVERFLOW:10000)
+				define('_DIRECT_CRON_FORCE',true);
+		}
+	}
+
+	queue_set_next_job_time($next);
+	$deja_la = false;
+}
+
+
+/**
+ * Mettre a jour la date de prochain job
+ * @param int $next
+ */
+function queue_set_next_job_time($next) {
+
+	// utiliser le temps courant reel plutot que temps de la requete ici
+	$time = time();
+
+	// toujours relire la valeur pour comparer, pour tenir compte des maj concourrantes
+	// et ne mettre a jour que si il y a un interet a le faire
+	// permet ausis d'initialiser le nom de fichier a coup sur
+	$curr_next = $_SERVER['REQUEST_TIME'] + queue_sleep_time_to_next_job(true);
+	if (
+			($curr_next<$time AND $next>$time) // le prochain job est dans le futur mais pas la date planifiee actuelle
+			OR $curr_next>$next // le prochain job est plus tot que la date planifiee actuelle
+		) {
+		if (include_spip('inc/memoization') AND defined('_MEMOIZE_MEMORY') AND _MEMOIZE_MEMORY) {
+			cache_set(_JQ_NEXT_JOB_TIME_FILENAME,intval($next));
+		}
+		else {
+			ecrire_fichier(_JQ_NEXT_JOB_TIME_FILENAME,intval($next));
+		}
+		queue_sleep_time_to_next_job($next);
+	}
+
+	return queue_sleep_time_to_next_job();
+}
+
+/**
+ * html a ajouter a la page pour declencher le cron
+ * ou rien si on a reussi a le lancer en asynchrone
+ * @return string
+ */
+function queue_affichage_cron(){
+	$texte = "";
+
+	// rien a faire si le prochain job est encore dans le futur
+	if (queue_sleep_time_to_next_job() OR defined('_DEBUG_BLOCK_QUEUE'))
+		return $texte;
+
+	// il y a des taches en attentes
+
+	// Si fsockopen est possible, on lance le cron via un socket
+	// en asynchrone
+	if(function_exists('fsockopen')){
+		$url = generer_url_action('cron','',false,true);
+		$parts=parse_url($url);
+
+		$fp = @fsockopen($parts['host'],
+	        isset($parts['port'])?$parts['port']:80,
+	        $errno, $errstr, 30);
+
+		if ($fp) {
+			$query = $parts['path'].($parts['query']?"?".$parts['query']:"");
+			$out = "GET ".$query." HTTP/1.1\r\n";
+			$out.= "Host: ".$parts['host']."\r\n";
+			$out.= "Connection: Close\r\n\r\n";
+			fwrite($fp, $out);
+			fclose($fp);
+			return $texte;
+		}
+	}
+
+	// ici lancer le cron par un CURL asynchrone si CURL est présent
+	// TBD
+
+	// si deja force, on retourne sans rien
+	if (defined('_DIRECT_CRON_FORCE'))
+		return $texte;
+	// si c'est un bot
+	// inutile de faire un appel par image background,
+	// on force un appel direct en fin de hit
+	if ((defined('_IS_BOT') AND _IS_BOT)){
+		define('_DIRECT_CRON_FORCE',true);
+		return $texte;
+	}
+
+	// en derniere solution, on insere une image background dans la page
+	$texte = '<!-- SPIP-CRON --><div style="background-image: url(\'' .
+		generer_url_action('cron') .
+		'\');"></div>';
+
+	return $texte;
+}
+?>
\ No newline at end of file
diff --git a/ecrire/inc/utils.php b/ecrire/inc/utils.php
index 2e49c22ee68215f331fad8de656376a6411db172..e81937f381d1f4aecaea526e15255b0df451ec8a 100644
--- a/ecrire/inc/utils.php
+++ b/ecrire/inc/utils.php
@@ -554,49 +554,129 @@ function action_cron() {
 	include_spip('inc/headers');
 	http_status(204); // No Content
 	header("Connection: close");
-	cron (2);
-}
-
-// cron() : execution des taches de fond
-// Le premier argument indique l'intervalle demande entre deux taches
-// par defaut, 60 secondes (quand il est appele par public.php)
-// il vaut 2 quand il est appele par ?action=cron, voire 0 en urgence
-// On peut lui passer en 2e arg le tableau de taches attendu par inc_genie()
-// Retourne Vrai si un tache a pu etre effectuee
-
-// http://doc.spip.org/@cron
-function cron ($gourmand=false, $taches= array()) {
-
-	// Si on est gourmand, ou si le fichier gourmand n'existe pas
-	// ou est trop vieux (> 60 sec), on va voir si un cron est necessaire.
-	// Au passage si on est gourmand on le dit aux autres
-	if (spip_touch(_DIR_TMP.'cron.lock-gourmand', 60, $gourmand)
-	OR ($gourmand!==false)) {
-
-	// Le fichier cron.lock indique la date de la derniere tache
-	// Il permet d'imposer qu'il n'y ait qu'une tache a la fois
-	// et 2 secondes minimum entre chaque:
-	// ca soulage le serveur et ca evite
-	// les conflits sur la base entre taches.
-
-	if (spip_touch(_DIR_TMP.'cron.lock',
-			(is_int($gourmand) ? $gourmand : 2))) {
-			// Si base inaccessible, laisser tomber.
-			if (!spip_connect()) return false;
-
-			$genie = charger_fonction('genie', 'inc', true);
-			if ($genie) {
-				$genie($taches);
-				// redater a la fin du cron
-				// car il peut prendre plus de 2 secondes.
-				spip_touch(_DIR_TMP.'cron.lock', 0);
-				return true;
-			}
-		}# else spip_log("busy");
+	define('_DIRECT_CRON_FORCE',true);
+	cron();
+}
+
+/**
+ * cron() : execution des taches de fond
+ * On peut lui passer en 1er (ou 2e arg pour compat)
+ * le tableau de taches attendu par inc_genie()
+ * Retourne Vrai si un tache a pu etre effectuee
+ * pas de verrou ici : les verrous sont geres sur chaque tache
+ * a chaque execution
+ *
+ * http://doc.spip.org/@cron
+ *
+ * @param array $taches
+ *   taches forcees
+ * @param array $taches_old
+ *   taches forcees, pour compat avec ancienne syntaxe
+ * @return bool
+ */
+function cron ($taches=array(), $taches_old= array()) {
+	// si pas en mode cron force
+	// ou si base inaccessible, laisser tomber.
+	if (!defined('_DIRECT_CRON_FORCE') OR !spip_connect()) return false;
+	spip_log("cron !",'jq'._LOG_DEBUG);
+	if (!is_array($taches)) $taches = $taches_old; // compat anciens appels
+	if ($genie = charger_fonction('genie', 'inc', true)) {
+		$genie($taches);
+		return true;
 	}
 	return false;
 }
 
+/**
+ * Ajout d'une tache dans la file d'attente
+ *
+ * @param $function
+ *   The function name to call.
+ * @param $description
+ *   A human-readable description of the queued job.
+ * @param $arguments
+ *   Optional array of arguments to pass to the function.
+ * @param $file
+ *   Optional file path which needs to be included for $fucntion.
+ * @param $no_duplicate
+ *   If TRUE, do not add the job to the queue if one with the same function and
+ *   arguments already exists.
+ * @param $time
+ *		time for starting the job. If 0, job will start as soon as possible
+ * @param $priority
+ *		-10 (low priority) to +10 (high priority), 0 is the default
+ * @return int
+ *	id of job
+ */
+function job_queue_add($function, $description, $arguments = array(), $file = '', $no_duplicate = FALSE, $time=0, $priority=0) {
+	include_spip('inc/queue');
+	return queue_add_job($function, $description, $arguments, $file, $no_duplicate, $time, $priority);
+}
+
+/**
+ * Supprimer une tache de la file d'attente
+ * @param int $id_job
+ *  id of jonb to delete
+ * @return bool
+ */
+function job_queue_remove($id_job){
+	include_spip('inc/queue');
+	return queue_remove_job($id_job);
+}
+
+/**
+ * Associer une tache a un/des objets de SPIP
+ * @param int $id_job
+ *	id of job to link
+ * @param array $objets
+ *  can be a simple array('objet'=>'article','id_objet'=>23)
+ *  or an array of simple array to link multiples objet in one time
+ */
+function job_queue_link($id_job,$objets){
+	include_spip('inc/queue');
+	return queue_link_job($id_job,$objets);
+}
+
+
+/**
+ * Renvoyer le temps de repos restant jusqu'au prochain job
+ * 0 si un job est a traiter
+ * null si la queue n'est pas encore initialise
+ * $force est utilisee par queue_set_next_job_time() pour maj la valeur
+ *  - si true, force la relecture depuis le fichier
+ *  - si int, affecte la static directement avec la valeur
+ *
+ * @staticvar int $queue_next_job_time
+ * @param int/bool $force_next
+ * @return int
+ */
+function queue_sleep_time_to_next_job($force=null) {
+	static $queue_next_job_time = -1;
+	if ($force===true)
+		$queue_next_job_time = -1;
+	elseif ($force)
+		$queue_next_job_time = $force;
+
+	if ($queue_next_job_time==-1) {
+		define('_JQ_NEXT_JOB_TIME_FILENAME',_DIR_TMP . "job_queue_next.txt");
+		// utiliser un cache memoire si dispo
+		if (include_spip('inc/memoization') AND defined('_MEMOIZE_MEMORY') AND _MEMOIZE_MEMORY) {
+			$queue_next_job_time = cache_get(_JQ_NEXT_JOB_TIME_FILENAME);
+		}
+		else {
+			$queue_next_job_time = null;
+			if (lire_fichier(_JQ_NEXT_JOB_TIME_FILENAME, $contenu))
+				$queue_next_job_time = intval($contenu);
+		}
+	}
+
+	if (is_null($queue_next_job_time))
+		return null;
+	if (!$_SERVER['REQUEST_TIME'])
+		$_SERVER['REQUEST_TIME'] = time();
+	return max(0,$queue_next_job_time-$_SERVER['REQUEST_TIME']);
+}
+
 
 // transformation XML des "&" en "&amp;"
 // http://doc.spip.org/@quote_amp
diff --git a/ecrire/inc_version.php b/ecrire/inc_version.php
index 421b1ce230e43354bdd5fee0f72470398b8b0807..9ba774441169c6d47495d31034009f02c69a7a59 100644
--- a/ecrire/inc_version.php
+++ b/ecrire/inc_version.php
@@ -321,7 +321,7 @@ $spip_version_branche = "2.3.0-dev";
 // (= numero SVN de leur derniere modif cassant la compatibilite et/ou necessitant un recalcul des squelettes)
 $spip_version_code = 17563;
 // version de la base SQL (= numero SVN de sa derniere modif)
-$spip_version_base = 17563;
+$spip_version_base = 17577;
 
 // version de l'interface a la base
 $spip_sql_version = 1;
diff --git a/ecrire/lang/ecrire_fr.php b/ecrire/lang/ecrire_fr.php
index 230c7e2ee3a196f9f68db987a3b67569794590b8..2955a9a58d02422fdc5bfc4f939575fb34d3d428 100644
--- a/ecrire/lang/ecrire_fr.php
+++ b/ecrire/lang/ecrire_fr.php
@@ -986,6 +986,14 @@ dans une couleur qui indique leur état :',
 'plugins_vue_liste' => 'Liste',
 'protocole_ldap' => 'Version du protocole :',
 
+// Q
+'queue_executer_maintenant' => 'Exécuter maintenant',
+'queue_nb_jobs_in_queue' => '@nb@ travaux en attente',
+'queue_next_job_in_nb_sec' => 'Prochain travail dans @nb@ s',
+'queue_one_job_in_queue' => '1 travail en attente',
+'queue_purger_queue' => 'Purger la liste des travaux',
+'queue_titre' => 'Liste de travaux',
+
 // R
 'repertoire_plugins' => 'Répertoire :',
 
diff --git a/ecrire/maj/svn10000.php b/ecrire/maj/svn10000.php
index 2702b9f7c5469a79607896a20fbbf85a2c173da0..d269871e61b86bcde4f960f287d71ffdc8e2d7e4 100644
--- a/ecrire/maj/svn10000.php
+++ b/ecrire/maj/svn10000.php
@@ -342,4 +342,8 @@ $GLOBALS['maj'][17563] = array(
 	array('sql_update','spip_articles',array('virtuel'=>'SUBSTRING(chapo,2)','chapo'=>"''"),"chapo LIKE '=_%'"),
 );
 
+$GLOBALS['maj'][17577] = array(
+	array('maj_tables',array('spip_jobs','spip_jobs_liens')),
+);
+
 ?>
\ No newline at end of file
diff --git a/ecrire/public.php b/ecrire/public.php
index f95123f5ff001424b7ba9c07858767fbfeea0567..fa3f6f10ef0ef2cd4a4836e0582ebfc72fafe9ca 100644
--- a/ecrire/public.php
+++ b/ecrire/public.php
@@ -207,15 +207,8 @@ if (isset($GLOBALS['_INC_PUBLIC'])) {
 		}
 
 		// Effectuer une tache de fond ?
-		// si #SPIP_CRON est present, on ne le tente que pour les navigateurs
-		// en mode texte (par exemple), et seulement sur les pages web
-		if (defined('_DIRECT_CRON_FORCE')
-			OR (
-			!defined('_DIRECT_CRON_INHIBE')
-			AND $html
-			AND !strstr($page['texte'], '<!-- SPIP-CRON -->')
-			AND !preg_match(',msie|mozilla|opera|konqueror,i', $_SERVER['HTTP_USER_AGENT']))
-			)
+		// si _DIRECT_CRON_FORCE est present, on force l'appel
+		if (defined('_DIRECT_CRON_FORCE'))
 			cron();
 
 		// sauver le cache chemin si necessaire
diff --git a/ecrire/public/balises.php b/ecrire/public/balises.php
index 2c3e93e58f1083cd78909c85f1c7b67e6c366084..0e9cc0987cbe576c3d1bfb075fa0358b68857159 100644
--- a/ecrire/public/balises.php
+++ b/ecrire/public/balises.php
@@ -330,20 +330,6 @@ function balise_FIN_SURLIGNE_dist($p) {
 }
 
 
-// #SPIP_CRON
-// a documenter
-// insere un <div> avec un lien background-image vers les taches de fond.
-// Si cette balise est presente sur la page de sommaire, le site ne devrait
-// quasiment jamais se trouver ralenti par des taches de fond un peu lentes
-// http://doc.spip.org/@balise_SPIP_CRON_dist
-function balise_SPIP_CRON_dist ($p) {
-	$p->code = '"<!-- SPIP-CRON --><div style=\"background-image: url(\'' .
-		generer_url_action('cron') .
-		'\');\"></div>"';
-	$p->interdire_scripts = false;
-	return $p;
-}
-
 // #INTRODUCTION
 // #INTRODUCTION{longueur}
 // http://www.spip.net/@introduction
diff --git a/prive/modeles/object_jobs_list.html b/prive/modeles/object_jobs_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..ad7025788d15bea614872fa96105456c222b3933
--- /dev/null
+++ b/prive/modeles/object_jobs_list.html
@@ -0,0 +1,17 @@
+<B_jobs>
+	<div class="jobs_liste jobs_liste_#ENV{objet}">
+	#ANCRE_PAGINATION
+	<ul class="liste_items jobs">
+		<BOUCLE_jobs(JOBS){par date}{pagination 5}{objet}{id_objet}>
+			<li class="item">
+				<div class="date">[(#DATE|date_relative)]</div>
+				[<strong class="description">(#DESCRIPTIF|PtoBR)</strong>]
+				[(#AUTORISER{'annuler','job',#ID_JOB}|oui)
+				<div class="actions">[(#BOUTON_ACTION{<:annuler:>,#URL_ACTION_AUTEUR{annuler_job,#ID_JOB,#SELF},ajax})]</div>
+				]
+			</li>
+		</BOUCLE_jobs>
+	</ul>
+	[<p class="pagination">(#PAGINATION)</p>]
+	</div>
+</B_jobs>
\ No newline at end of file
diff --git a/prive/squelettes/contenu/job_queue.html b/prive/squelettes/contenu/job_queue.html
new file mode 100644
index 0000000000000000000000000000000000000000..e4a0c7a8f333493452ddbdfd67a06fa0cc7a59bf
--- /dev/null
+++ b/prive/squelettes/contenu/job_queue.html
@@ -0,0 +1,41 @@
+#CACHE{0}
+[(#AUTORISER{administrer,queue}|sinon_interdire_acces)]
+<?php
+// bloquer la queue sur ce hit
+// pour avoir coherence entre l'affichage de la liste de jobs
+// et les jobs en base en fin de hit
+define('_DEBUG_BLOCK_QUEUE',true);
+include_spip('inc/genie');
+genie_queue_watch_dist();
+?>
+<h1><:queue_titre:></h1>
+<B_jobs>
+	<h3>[(#GRAND_TOTAL|singulier_ou_pluriel{queue_one_job_in_queue,queue_nb_jobs_in_queue})]</h3>
+	<p>#SET{nb,#REM|queue_sleep_time_to_next_job}<:queue_next_job_in_nb_sec{nb=#GET{nb}}:></p>
+	[<p class="pagination">(#PAGINATION{prive})</p>]
+	<ul class="liste-items">
+		<BOUCLE_jobs(jobs){par date}{pagination 20}>
+			<li class="item[ (#STATUS|?{'scheduled','pending'})]">
+				<div class="date">[(#DATE|date_relative)][ (#STATUS|non)(en cours)][(#PRIORITE|oui)&#91;#PRIORITE&#93;]</div>
+				[<strong class="description">(#DESCRIPTIF|PtoBR)</strong>]
+				[<span class="small">| #FONCTION((#ARGS|unserialize|implode{','}))</span>]
+				[(#AUTORISER{'annuler','job',#ID_JOB}|oui)
+				<div class="actions">
+					[(#BOUTON_ACTION{<:annuler:>,#URL_ACTION_AUTEUR{annuler_job,#ID_JOB,#SELF},ajax})]
+					[(#BOUTON_ACTION{<:queue_executer_maintenant:>,#URL_ACTION_AUTEUR{forcer_job,#ID_JOB,#SELF},ajax})]
+				</div>
+				]
+			</li>
+		</BOUCLE_jobs>
+	</ul>
+	[<p class="pagination">(#PAGINATION{prive})</p>]
+	[(#AUTORISER{'purger','queue'}|oui)
+	<div class="actions">
+		[(#BOUTON_ACTION{<:queue_purger_queue:>,#URL_ACTION_AUTEUR{purger_queue,'',#SELF},ajax})]
+	</div>
+	]
+</B_jobs>
+<script type="text/javascript">/*<![CDATA[*/
+function queue_reload(){jQuery('h1').ajaxReload();}
+if (window.jQuery) setTimeout(queue_reload,60000);
+/*]]>*/</script>
\ No newline at end of file
diff --git a/prive/themes/spip/images/queue-process-16.png b/prive/themes/spip/images/queue-process-16.png
new file mode 100644
index 0000000000000000000000000000000000000000..54805d4d9875ac5bd10194a74d685721aae72a33
Binary files /dev/null and b/prive/themes/spip/images/queue-process-16.png differ
diff --git a/prive/themes/spip/images/queue-process-24.png b/prive/themes/spip/images/queue-process-24.png
new file mode 100644
index 0000000000000000000000000000000000000000..e55cd17cdd1f6282ce1fedc0907054f3f186b8f2
Binary files /dev/null and b/prive/themes/spip/images/queue-process-24.png differ
diff --git a/prive/themes/spip/images/queue-process-32.png b/prive/themes/spip/images/queue-process-32.png
new file mode 100644
index 0000000000000000000000000000000000000000..da5b0d5eb929bae9c23a856913eeea89e9ea0677
Binary files /dev/null and b/prive/themes/spip/images/queue-process-32.png differ