Browse Source
- la base évolue pour inclure un champ 'destinataires' qui conserve une trace en liste texte separee par des virgules des id_auteur ou email des destinataires. Permet notamment de le memoriser sur les messages en statut prepa - gestion complète de l'envoi/modification des messages, penses-betes et annonce - notification des auteurs qui ont une adresse mail, en complement de la diffusion du message dans la messagerie interne - l'envoi de messages vers des email externes (email comme destinatire) est egalement possible si la configuration est activee (le form de config est à completer) - gestion des messages lus, des alertes reception message - choix des destinataires par saisie libre+autocompletion (mix entre code du selecteur generique et du plugin messagerie) - gestion des messages lus Il reste principalement : - la gestion de la suppression des messages - l'interface de saisie des messages "RDV" qui comportent une date de debut+date de finsvn/root/tags/plugins/organiseur/0.5.0 v0.5.0

31 changed files with 1473 additions and 389 deletions
@ -1,202 +0,0 @@
|
||||
<?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; |
||||
|
||||
include_spip('inc/filtres'); |
||||
|
||||
// http://doc.spip.org/@action_editer_message_dist |
||||
function action_editer_message_dist($arg=null) { |
||||
|
||||
if (is_null($arg)){ |
||||
$securiser_action = charger_fonction('securiser_action', 'inc'); |
||||
$arg = $securiser_action(); |
||||
} |
||||
|
||||
if (preg_match(',^(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_vieux($arg); |
||||
elseif (preg_match(',^-(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_supprimer($r[1]); |
||||
elseif (preg_match(',^(\d+)\W$,', $arg, $r)) |
||||
action_editer_message_post_choisir($r[1]); |
||||
elseif (preg_match(',^(\d+)\W@(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_ajouter($r[1], $r[2]); |
||||
elseif (preg_match(',^(\d+)\W:(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_vu($r[1], $r[2]); |
||||
elseif (preg_match(',^(\d+)\W-(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_retirer($r[1], $r[2]); |
||||
elseif (preg_match(',^(\d+)\W(\w+)$,', $arg, $r)) |
||||
action_editer_message_post_envoyer($r[1], $r[2]); |
||||
elseif (preg_match(',^(\w+)$,', $arg, $r)) |
||||
action_editer_message_post_nouveau($arg); |
||||
elseif (preg_match(',^(\w+)\W(\d+)$,', $arg, $r)) |
||||
action_editer_message_post_nouveau($r[1], $r[2]); |
||||
elseif (preg_match(',^(\w+)\W(\d+-\d+-\d+)$,', $arg, $r)) |
||||
action_editer_message_post_nouveau($r[1], '', $r[2]); |
||||
else spip_log("action_editer_message_dist $arg pas compris"); |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_supprimer |
||||
function action_editer_message_post_supprimer($id_message) { |
||||
sql_delete("spip_messages", "id_message=".intval($id_message)); |
||||
include_spip('action/editer_auteur'); |
||||
auteur_dissocier('*', array('message'=>$id_message)); |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_vu |
||||
function action_editer_message_post_vu($id_message, $id_auteur) { |
||||
include_spip('action/editer_auteur'); |
||||
auteur_qualifier($id_auteur,array('message'=>$id_message),array("vu" => 'oui')); |
||||
|
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_retirer |
||||
function action_editer_message_post_retirer($id_message, $id_auteur) { |
||||
include_spip('action/editer_auteur'); |
||||
auteur_dissocier($id_auteur, array('message'=>$id_message)); |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_ajouter |
||||
function action_editer_message_post_ajouter($id_message, $id_auteur) { |
||||
include_spip('action/editer_auteur'); |
||||
auteur_associer($id_auteur, array('message'=>$id_message),array('vu'=>'non')); |
||||
// Ne pas notifier ici, car si on se trompe d'auteur, on veut avoir le temps |
||||
// de supprimer celui qu'on vient d'ajouter... c'est fait en cron |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_choisir |
||||
function action_editer_message_post_choisir($id_message) { |
||||
|
||||
if ($id_auteur = _request('nouv_auteur')) |
||||
action_editer_message_post_ajouter($id_message, $id_auteur); |
||||
else { |
||||
include_spip('inc/ressembler'); |
||||
include_spip('inc/charsets'); // pour tranlitteration |
||||
$id_auteur = $GLOBALS['visiteur_session']['id_auteur']; |
||||
$cherche_auteur= _request('cherche_auteur'); |
||||
$query = sql_select("id_auteur, nom", "spip_auteurs", "messagerie<>'non' AND id_auteur<>'$id_auteur' AND pass<>'' AND login<>''"); |
||||
$table_auteurs = array(); |
||||
$table_ids = array(); |
||||
while ($row = sql_fetch($query)) { |
||||
$table_auteurs[] = $row['nom']; |
||||
$table_ids[] = $row['id_auteur']; |
||||
} |
||||
$res = mots_ressemblants($cherche_auteur, $table_auteurs, $table_ids); |
||||
$n = count($res); |
||||
|
||||
if ($n == 1) |
||||
# Bingo |
||||
action_editer_message_post_ajouter($id_message, $res[0]); |
||||
# renvoyer la valeur ==> formulaire de choix si n !=1 |
||||
# notification que $res[0] a ete rajoute sinon |
||||
redirige_par_entete(parametre_url(urldecode(_request('redirect')), |
||||
'cherche_auteur', $cherche_auteur, '&')); |
||||
} |
||||
} |
||||
|
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_envoyer |
||||
function action_editer_message_post_envoyer($id_message, $statut) { |
||||
|
||||
sql_updateq("spip_messages", array("statut" => $statut), "id_message=$id_message"); |
||||
sql_updateq("spip_messages", array("date_heure" => date('Y-m-d H:i:s')), "id_message=$id_message AND rv<>'oui'"); |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_nouveau |
||||
function action_editer_message_post_nouveau($type, $dest='', $rv='') |
||||
{ |
||||
|
||||
$id_auteur = $GLOBALS['visiteur_session']['id_auteur']; |
||||
|
||||
$mydate = date("YmdHis", time() - 2 * 24 * 3600); |
||||
sql_delete("spip_messages", "(statut = 'redac') AND (date_heure < $mydate)"); |
||||
|
||||
if ($type == 'pb') $statut = 'publie'; |
||||
else $statut = 'redac'; |
||||
|
||||
$titre = filtrer_entites(_T('texte_nouveau_message')); |
||||
|
||||
$vals = array('titre' => $titre, |
||||
'statut' => $statut, |
||||
'type' => $type, |
||||
'id_auteur' => $id_auteur); |
||||
|
||||
if (!$rv) |
||||
$vals['date_heure'] = date('Y-m-d H:i:s'); |
||||
else { |
||||
$vals['date_heure'] = "$rv 12:00:00"; |
||||
$vals['date_fin'] = "$rv 13:00:00"; |
||||
$vals['rv'] = 'oui'; |
||||
} |
||||
|
||||
$id_message = sql_insertq("spip_messages", $vals); |
||||
|
||||
include_spip('action/editer_auteur'); |
||||
|
||||
if ($type != "affich"){ |
||||
auteur_associer($id_auteur, array('message'=>$id_message),array('vu'=>'oui')); |
||||
if ($dest) |
||||
auteur_associer($dest, array('message'=>$id_message),array('vu'=>'non')); |
||||
} |
||||
|
||||
redirige_url_ecrire('message_edit', "id_message=$id_message&new=oui&dest=$dest"); |
||||
} |
||||
|
||||
// http://doc.spip.org/@action_editer_message_post_vieux |
||||
function action_editer_message_post_vieux($id_message) |
||||
{ |
||||
sql_updateq('spip_messages', array('titre'=>_request('titre'), 'texte' => _request('texte')), "id_message=$id_message"); |
||||
|
||||
sql_updateq('spip_messages', array('rv' => _request('rv')), "id_message=$id_message"); |
||||
|
||||
if (_request('jour')) |
||||
change_date_message($id_message, _request('heures'),_request('minutes'),_request('mois'), _request('jour'), _request('annee'), _request('heures_fin'),_request('minutes_fin'),_request('mois_fin'), _request('jour_fin'), _request('annee_fin')); |
||||
} |
||||
|
||||
|
||||
// Convertir dates a calendrier correct |
||||
// (exemple: 31 fevrier devient debut mars, 24h12 devient 00h12 du lendemain) |
||||
|
||||
// http://doc.spip.org/@change_date_message |
||||
function change_date_message($id_message, $heures,$minutes,$mois, $jour, $annee, $heures_fin,$minutes_fin,$mois_fin, $jour_fin, $annee_fin) |
||||
{ |
||||
$date = date("Y-m-d H:i:s", mktime($heures,$minutes,0,$mois, $jour, $annee)); |
||||
|
||||
$jour = journum($date); |
||||
$mois = mois($date); |
||||
$annee = annee($date); |
||||
$heures = heures($date); |
||||
$minutes = minutes($date); |
||||
|
||||
// Verifier que la date de fin est bien posterieure au debut |
||||
$unix_debut = date("U", mktime($heures,$minutes,0,$mois, $jour, $annee)); |
||||
$unix_fin = date("U", mktime($heures_fin,$minutes_fin,0,$mois_fin, $jour_fin, $annee_fin)); |
||||
if ($unix_fin <= $unix_debut) { |
||||
$jour_fin = $jour; |
||||
$mois_fin = $mois; |
||||
$annee_fin = $annee; |
||||
$heures_fin = $heures + 1; |
||||
$minutes_fin = $minutes; |
||||
} |
||||
|
||||
$date_fin = date("Y-m-d H:i:s", mktime($heures_fin,$minutes_fin,0,$mois_fin, $jour_fin, $annee_fin)); |
||||
|
||||
$jour_fin = journum($date_fin); |
||||
$mois_fin = mois($date_fin); |
||||
$annee_fin = annee($date_fin); |
||||
$heures_fin = heures($date_fin); |
||||
$minutes_fin = minutes($date_fin); |
||||
|
||||
sql_updateq('spip_messages', array('date_heure'=>"$annee-$mois-$jour $heures:$minutes:00", 'date_fin'=>"$annee_fin-$mois_fin-$jour_fin $heures_fin:$minutes_fin:00"), "id_message=$id_message"); |
||||
} |
||||
|
||||
?> |
@ -0,0 +1,26 @@
|
||||
<?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; |
||||
|
||||
function action_quete_autocomplete_dist(){ |
||||
$securiser_action = charger_fonction('securiser_action','inc'); |
||||
$arg = $securiser_action(); |
||||
if ($arg |
||||
AND $arg==$GLOBALS['visiteur_session']['id_auteur']){ |
||||
include_spip('inc/actions'); |
||||
echo ajax_retour( |
||||
recuperer_fond('prive/squelettes/inclure/organiseur-autocomplete-auteur',array('q'=>_request('q'))), |
||||
'text/plain' |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
<div class="ajax formulaire_spip formulaire_editer formulaire_#FORM formulaire_#FORM-#ENV{id,nouveau}"> |
||||
[<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>] |
||||
[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>] |
||||
[(#ENV{editable}) |
||||
<form method='post' action='#ENV{action}'><div> |
||||
[(#REM) declarer les hidden qui declencheront le service du formulaire |
||||
parametre : url d'action ] |
||||
#ACTION_FORMULAIRE{#ENV{action}} |
||||
#SET{fl,organiseur} |
||||
<ul> |
||||
[(#ENV{_destiner}|oui) |
||||
#SET{name,destinataires}#SET{obli,'obligatoire'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} |
||||
<li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]"> |
||||
<label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[ |
||||
<span class='erreur_message'>(#GET{erreurs})</span> |
||||
] |
||||
#INCLURE{fond=formulaires/inc-destinataires-message,name=#GET{name},env} |
||||
</li> |
||||
] |
||||
#SET{name,titre}#SET{obli,'obligatoire'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} |
||||
<li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]"> |
||||
<label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[ |
||||
<span class='erreur_message'>(#GET{erreurs})</span> |
||||
]<input type="text" class="text" name="#GET{name}" value="#ENV*{#GET{name}}" id="#GET{name}" [(#HTML5|et{#GET{obli}})required='required']/> |
||||
</li> |
||||
#SET{name,texte}#SET{obli,'obligatoire'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} |
||||
<li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]"> |
||||
<label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[ |
||||
<span class='erreur_message'>(#GET{erreurs})</span> |
||||
]<textarea class="textarea" name="#GET{name}" rows="10"> |
||||
#ENV*{#GET{name}}</textarea> |
||||
</li> |
||||
[(#REM) Piege a robots spammeurs ] |
||||
#SET{name,antispam}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} |
||||
<li class="editer none editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]"> |
||||
<label for="nobotnobot-#ID"><:antispam_champ_vide:></label>[ |
||||
<span class='erreur_message'>(#GET{erreurs})</span> |
||||
]<input type="text" class="text" name="#GET{name}" value="#ENV*{#GET{name}}" id="nobotnobot-#ID" /> |
||||
</li> |
||||
</ul> |
||||
<!--extra--> |
||||
<p class='boutons'><span class='image_loading'> </span> |
||||
[(#ENV{statut}|=={publie}|non) |
||||
<input type='submit' class='submit' name="draft" value='<:organiseur:bouton_enregistrer_brouillon:>' /> |
||||
] |
||||
<input type='submit' class='submit' name="send" value='<:organiseur:bouton_envoyer_message:>' /> |
||||
</p> |
||||
</div></form> |
||||
] |
||||
</div> |
||||
[(#ENV{_destiner}|oui) |
||||
<script type="text/javascript"> |
||||
if (typeof formulaire_editer_message_init=="undefined"){ |
||||
var formulaire_editer_message_init=''; // eviter double dl si plusieurs forms dans la page |
||||
var url_trouver_destinataire = '[(#URL_ACTION_AUTEUR{quete_autocomplete,#SESSION{id_auteur}}|replace{&,&})]'; |
||||
jQuery.getScript('#CHEMIN{javascript/jquery.autocomplete.js}',function(){ |
||||
jQuery.getScript('#CHEMIN{formulaires/editer_message.js}'); |
||||
}); |
||||
} |
||||
</script> |
||||
] |
@ -0,0 +1,39 @@
|
||||
function formulaire_editer_message_set_dest(input,data,value){ |
||||
console.log(data); |
||||
console.log(value); |
||||
var id_auteur; |
||||
var box = jQuery(input).siblings('.selected'); |
||||
if (data[1]) { |
||||
id_auteur = data[1]; |
||||
var nom = value; |
||||
if (box.find('input[value='+id_auteur+']').length==0){ |
||||
box.find('.on').removeClass('on'); |
||||
box.append(" <span class='dest on'>" |
||||
+ value |
||||
+"<input type='hidden' name='" |
||||
+ jQuery(input).attr('data-name') |
||||
+ "' value='"+id_auteur+"' /> " |
||||
+ $(box).find('span.dest:first').html() |
||||
+"</span>"); |
||||
} |
||||
else |
||||
box.find('input[value='+id_auteur+']').closest('span').addClass('on').siblings('.on').removeClass('on'); |
||||
} |
||||
jQuery(input).attr('value','');//.get(0).focus();
|
||||
} |
||||
function formulaire_editer_message_init(){ |
||||
jQuery("input.destinataires:not(.autocompleted)").each(function(){ |
||||
var me = this; |
||||
jQuery(me) |
||||
.autocomplete(url_trouver_destinataire, {minChars:2, mustMatchOrEmpty:1,autoFill:true,matchSubset:0, matchContains:1, cacheLength:10 }) |
||||
.bind('result',function(e,data,value){return formulaire_editer_message_set_dest(me,data,value);}) |
||||
.parent().bind('click',function(){jQuery(me).get(0).focus();}); |
||||
}) |
||||
.addClass('autocompleted'); |
||||
} |
||||
if (window.jQuery){ |
||||
jQuery(function(){ |
||||
formulaire_editer_message_init(); |
||||
onAjaxLoad(formulaire_editer_message_init); |
||||
}); |
||||
} |
@ -0,0 +1,114 @@
|
||||
<?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; |
||||
|
||||
include_spip('inc/editer'); |
||||
|
||||
|
||||
function formulaires_editer_message_charger_dist($id_message='new',$type='message',$retour='',$accepter_email='oui',$destinataires='',$titre='',$texte=''){ |
||||
|
||||
if (!autoriser('envoyermessage',$type)) |
||||
return false; |
||||
|
||||
$valeurs = formulaires_editer_objet_charger('message',$id_message,0,0,$retour,''); |
||||
|
||||
// les destinataires sont stockes en chaine separe par une virgule dans la base |
||||
if (strlen($valeurs['destinataires'])) |
||||
$valeurs['destinataires'] = explode(",",$valeurs['destinataires']); |
||||
|
||||
if (!intval($id_message)){ |
||||
$valeurs['type'] = $type; |
||||
$valeurs['destinataires'] = ($destinataires ? explode(",",$destinataires):array()); |
||||
$valeurs['titre'] = $titre; |
||||
$valeurs['texte'] = $texte; |
||||
} |
||||
|
||||
if (in_array($valeurs['type'],array('pb','affich'))) |
||||
$valeurs['_destiner'] = ''; |
||||
else |
||||
$valeurs['_destiner'] = ' '; |
||||
|
||||
return $valeurs; |
||||
} |
||||
|
||||
|
||||
function formulaires_editer_message_verifier_dist($id_message='new',$type='message',$retour='',$accepter_email='oui',$destinataires='',$titre='',$texte=''){ |
||||
|
||||
$oblis = array('titre'); |
||||
if (!_request('draft')) |
||||
$oblis[] = 'texte'; |
||||
if (intval($id_message) AND $t=sql_getfetsel('type','spip_messages','id_message='.intval($id_message))) |
||||
$type = $t; |
||||
if (!in_array($type,array('pb','affich')) |
||||
// pas de destinataire obligatoire pour un brouillon |
||||
AND !_request('draft')) |
||||
$oblis['destinataires'] = 'destinataires'; |
||||
|
||||
if ($d=_request('destinataires')) |
||||
set_request('destinataires',implode(',',$d)); |
||||
$erreurs = formulaires_editer_objet_verifier('message',$id_message,$oblis); |
||||
if ($d) |
||||
set_request('destinataires',$d); |
||||
include_spip('inc/messages'); |
||||
if (!$erreurs['destinataires'] |
||||
AND isset($oblis['destinataires']) |
||||
AND $e = messagerie_verifier_destinataires(_request('destinataires'),array('accepter_email'=>($accepter_email=='oui')))) |
||||
$erreurs['destinataires'] = implode(', ',$e); |
||||
|
||||
return $erreurs; |
||||
} |
||||
|
||||
function formulaires_editer_message_traiter_dist($id_message='new',$type='message',$retour='',$accepter_email='oui',$destinataires='',$titre='',$texte=''){ |
||||
// preformater le post |
||||
// fixer le type de message |
||||
// sans modifier le type d'un message existant |
||||
if (intval($id_message) AND $t=sql_getfetsel('type','spip_messages','id_message='.intval($id_message))) |
||||
$type = $t; |
||||
set_request('type',$type); |
||||
|
||||
// formater les destinataires |
||||
$d = _request('destinataires'); |
||||
if (!$d) |
||||
$d = array(); |
||||
include_spip('inc/messages'); |
||||
$d = messagerie_nettoyer_destinataires($d); |
||||
// si email non acceptes, extraire les seuls id_auteur de la liste proposee |
||||
if ($accepter_email!=='oui'){ |
||||
// separer id_auteur et email |
||||
$d = messagerie_destiner($d); |
||||
// ne conserver que les id_auteur |
||||
$d = reset($d); |
||||
} |
||||
// reinjecter sous forme de chaine |
||||
set_request('destinataires',implode(',',$d)); |
||||
|
||||
// fixer l'auteur ! |
||||
set_request('id_auteur',$GLOBALS['visiteur_session']['id_auteur']); |
||||
|
||||
// on gere par les traitements standard |
||||
// la diffusion du message se fait par pipeline post_edition sur instituer |
||||
// et notification |
||||
$res = formulaires_editer_objet_traiter('message',$id_message,0,0,$retour,''); |
||||
if ($id_message = $res['id_message'] |
||||
AND !_request('draft')){ |
||||
include_spip('action/editer_objet'); |
||||
objet_modifier('message',$id_message,array('statut'=>'publie')); |
||||
// apres en message envoyes, retourner sur la boite d'envoi plutot que sur le message |
||||
if ($res['redirect']==generer_url_ecrire('message','id_message='.$id_message)) |
||||
$res['redirect'] = generer_url_ecrire('messages','quoi=envoi'); |
||||
} |
||||
|
||||
set_request('destinataires',explode(',',_request('destinataires'))); |
||||
return $res; |
||||
} |
||||
|
@ -0,0 +1,13 @@
|
||||
<div class="fake-input clearfix"> |
||||
<span class="selected"> |
||||
[(#REM) Premier element vide et cache qui sert de modele pour le js] |
||||
<span class="dest" style="display: none;"><img |
||||
src='#CHEMIN_IMAGE{supprimer-12.png}' width="12" height="12" onclick="jQuery(this).parent().remove();" /></span> |
||||
<BOUCLE_d(POUR){tableau #ENV{#ENV{name}}}> |
||||
<span class="dest"><input type="hidden" name="#ENV{name}[]" value="#VALEUR" /> |
||||
<BOUCLE_a(AUTEURS){id_auteur=#VALEUR}{tout}>#NOM</BOUCLE_a>#VALEUR<//B_a> <img |
||||
src='#CHEMIN_IMAGE{supprimer-12.png}' width="12" height="12" onclick="jQuery(this).parent().remove();" /></span> |
||||
</BOUCLE_d> |
||||
</span> |
||||
<input type="text" class="text destinataires" data-name="#ENV{name}[]" value="" id="#GET{name}" /> |
||||
</div> |
@ -0,0 +1,808 @@
|
||||
/* |
||||
* jQuery Autocomplete plugin 1.1 |
||||
* |
||||
* Copyright (c) 2009 Jorn Zaefferer |
||||
* |
||||
* Dual licensed under the MIT and GPL licenses: |
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
* |
||||
* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ |
||||
*/ |
||||
|
||||
;(function($) { |
||||
|
||||
$.fn.extend({ |
||||
autocomplete: function(urlOrData, options) { |
||||
var isUrl = typeof urlOrData == "string"; |
||||
options = $.extend({}, $.Autocompleter.defaults, { |
||||
url: isUrl ? urlOrData : null, |
||||
data: isUrl ? null : urlOrData, |
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10, |
||||
max: options && !options.scroll ? 10 : 150 |
||||
}, options); |
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; }; |
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem; |
||||
|
||||
return this.each(function() { |
||||
new $.Autocompleter(this, options); |
||||
}); |
||||
}, |
||||
result: function(handler) { |
||||
return this.bind("result", handler); |
||||
}, |
||||
search: function(handler) { |
||||
return this.trigger("search", [handler]); |
||||
}, |
||||
flushCache: function() { |
||||
return this.trigger("flushCache"); |
||||
}, |
||||
setOptions: function(options){ |
||||
return this.trigger("setOptions", [options]); |
||||
}, |
||||
unautocomplete: function() { |
||||
return this.trigger("unautocomplete"); |
||||
} |
||||
}); |
||||
|
||||
$.Autocompleter = function(input, options) { |
||||
|
||||
var KEY = { |
||||
UP: 38, |
||||
DOWN: 40, |
||||
DEL: 46, |
||||
TAB: 9, |
||||
RETURN: 13, |
||||
ESC: 27, |
||||
COMMA: 188, |
||||
PAGEUP: 33, |
||||
PAGEDOWN: 34, |
||||
BACKSPACE: 8 |
||||
}; |
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); |
||||
|
||||
var timeout; |
||||
var previousValue = ""; |
||||
var cache = $.Autocompleter.Cache(options); |
||||
var hasFocus = 0; |
||||
var lastKeyPressCode; |
||||
var config = { |
||||
mouseDownOnSelect: false |
||||
}; |
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config); |
||||
|
||||
var blockSubmit; |
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() { |
||||
if (blockSubmit) { |
||||
blockSubmit = false; |
||||
return false; |
||||
} |
||||
}); |
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { |
||||
// a keypress means the input has focus
|
||||
// avoids issue where input had focus before the autocomplete was applied
|
||||
hasFocus = 1; |
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode; |
||||
switch(event.keyCode) { |
||||
|
||||
case KEY.UP: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.prev(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.DOWN: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.next(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.PAGEUP: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.pageUp(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.PAGEDOWN: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.pageDown(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: |
||||
case KEY.TAB: |
||||
case KEY.RETURN: |
||||
if( selectCurrent() ) { |
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault(); |
||||
blockSubmit = true; |
||||
return false; |
||||
} |
||||
break; |
||||
|
||||
case KEY.ESC: |
||||
select.hide(); |
||||
break; |
||||
|
||||
default: |
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(onChange, options.delay); |
||||
break; |
||||
} |
||||
}).focus(function(){ |
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++; |
||||
}).blur(function() { |
||||
hasFocus = 0; |
||||
if (!config.mouseDownOnSelect) { |
||||
hideResults(); |
||||
} |
||||
}).click(function() { |
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) { |
||||
onChange(0, true); |
||||
} |
||||
}).bind("search", function() { |
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null; |
||||
function findValueCallback(q, data) { |
||||
var result; |
||||
if( data && data.length ) { |
||||
for (var i=0; i < data.length; i++) { |
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) { |
||||
result = data[i]; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if( typeof fn == "function" ) fn(result); |
||||
else $input.trigger("result", result && [result.data, result.value]); |
||||
} |
||||
$.each(trimWords($input.val()), function(i, value) { |
||||
request(value, findValueCallback, findValueCallback); |
||||
}); |
||||
}).bind("flushCache", function() { |
||||
cache.flush(); |
||||
}).bind("setOptions", function() { |
||||
$.extend(options, arguments[1]); |
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] ) |
||||
cache.populate(); |
||||
}).bind("unautocomplete", function() { |
||||
select.unbind(); |
||||
$input.unbind(); |
||||
$(input.form).unbind(".autocomplete"); |
||||
}); |
||||
|
||||
|
||||
function selectCurrent() { |
||||
var selected = select.selected(); |
||||
if( !selected ) |
||||
return false; |
||||
|
||||
var v = selected.result; |
||||
previousValue = v; |
||||
|
||||
if ( options.multiple ) { |
||||
var words = trimWords($input.val()); |
||||
if ( words.length > 1 ) { |
||||
var seperator = options.multipleSeparator.length; |
||||
var cursorAt = $(input).selection().start; |
||||
var wordAt, progress = 0; |
||||
$.each(words, function(i, word) { |
||||
progress += word.length; |
||||
if (cursorAt <= progress) { |
||||
wordAt = i; |
||||
return false; |
||||
} |
||||
progress += seperator; |
||||
}); |
||||
words[wordAt] = v; |
||||
// TODO this should set the cursor to the right position, but it gets overriden somewhere
|
||||
//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
|
||||
v = words.join( options.multipleSeparator ); |
||||
} |
||||
v += options.multipleSeparator; |
||||
} |
||||
|
||||
$input.val(v); |
||||
hideResultsNow(); |
||||
$input.trigger("result", [selected.data, selected.value]); |
||||
return true; |
||||
} |
||||
|
||||
function onChange(crap, skipPrevCheck) { |
||||
if( lastKeyPressCode == KEY.DEL ) { |
||||
select.hide(); |
||||
return; |
||||
} |
||||
|
||||
var currentValue = $input.val(); |
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue ) |
||||
return; |
||||
|
||||
previousValue = currentValue; |
||||
|
||||
currentValue = lastWord(currentValue); |
||||
if ( currentValue.length >= options.minChars) { |
||||
$input.addClass(options.loadingClass); |
||||
if (!options.matchCase) |
||||
currentValue = currentValue.toLowerCase(); |
||||
request(currentValue, receiveData, hideResultsNow); |
||||
} else { |
||||
stopLoading(); |
||||
select.hide(); |
||||
} |
||||
}; |
||||
|
||||
function trimWords(value) { |
||||
if (!value) |
||||
return [""]; |
||||
if (!options.multiple) |
||||
return [$.trim(value)]; |
||||
return $.map(value.split(options.multipleSeparator), function(word) { |
||||
return $.trim(value).length ? $.trim(word) : null; |
||||
}); |
||||
} |
||||
|
||||
function lastWord(value) { |
||||
if ( !options.multiple ) |
||||
return value; |
||||
var words = trimWords(value); |
||||
if (words.length == 1) |
||||
return words[0]; |
||||
var cursorAt = $(input).selection().start; |
||||
if (cursorAt == value.length) { |
||||
words = trimWords(value) |
||||
} else { |
||||
words = trimWords(value.replace(value.substring(cursorAt), "")); |
||||
} |
||||
return words[words.length - 1]; |
||||
} |
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){ |
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { |
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length)); |
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$(input).selection(previousValue.length, previousValue.length + sValue.length); |
||||
} |
||||
}; |
||||
|
||||
function hideResults() { |
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(hideResultsNow, 200); |
||||
}; |
||||
|
||||
function hideResultsNow() { |
||||
var wasVisible = select.visible(); |
||||
select.hide(); |
||||
clearTimeout(timeout); |
||||
stopLoading(); |
||||
if (options.mustMatch) { |
||||
// call search and run callback
|
||||
$input.search( |
||||
function (result){ |
||||
// if no value found, clear the input box
|
||||
if( !result ) { |
||||
if (options.multiple) { |
||||
var words = trimWords($input.val()).slice(0, -1); |
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); |
||||
} |
||||
else { |
||||
$input.val( "" ); |
||||
$input.trigger("result", null); |
||||
} |
||||
} |
||||
} |
||||
); |
||||
} |
||||
}; |
||||
|
||||
function receiveData(q, data) { |
||||
if ( data && data.length && hasFocus ) { |
||||
stopLoading(); |
||||
select.display(data, q); |
||||
autoFill(q, data[0].value); |
||||
select.show(); |
||||
} else { |
||||
hideResultsNow(); |
||||
} |
||||
}; |
||||
|
||||
function request(term, success, failure) { |
||||
if (!options.matchCase) |
||||
term = term.toLowerCase(); |
||||
var data = cache.load(term); |
||||
// recieve the cached data
|
||||
if (data && data.length) { |
||||
success(term, data); |
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
||||
|
||||
var extraParams = { |
||||
timestamp: +new Date() |
||||
}; |
||||
$.each(options.extraParams, function(key, param) { |
||||
extraParams[key] = typeof param == "function" ? param() : param; |
||||
}); |
||||
|
||||
$.ajax({ |
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort", |
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name, |
||||
dataType: options.dataType, |
||||
url: options.url, |
||||
data: $.extend({ |
||||
q: lastWord(term), |
||||
limit: options.max |
||||
}, extraParams), |
||||
success: function(data) { |
||||
var parsed = options.parse && options.parse(data) || parse(data); |
||||
cache.add(term, parsed); |
||||
success(term, parsed); |
||||
} |
||||
}); |
||||
} else { |
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList(); |
||||
failure(term); |
||||
} |
||||
}; |
||||
|
||||
function parse(data) { |
||||
var parsed = []; |
||||
var rows = data.split("\n"); |
||||
for (var i=0; i < rows.length; i++) { |
||||
var row = $.trim(rows[i]); |
||||
if (row) { |
||||
row = row.split("|"); |
||||
parsed[parsed.length] = { |
||||
data: row, |
||||
value: row[0], |
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0] |
||||
}; |
||||
} |
||||
} |
||||
return parsed; |
||||
}; |
||||
|
||||
function stopLoading() { |
||||
$input.removeClass(options.loadingClass); |
||||
}; |
||||
|
||||
}; |
||||
|
||||
$.Autocompleter.defaults = { |
||||
inputClass: "ac_input", |
||||
resultsClass: "ac_results", |
||||
loadingClass: "ac_loading", |
||||
minChars: 1, |
||||
delay: 400, |
||||
matchCase: false, |
||||
matchSubset: true, |
||||
matchContains: false, |
||||
cacheLength: 10, |
||||
max: 100, |
||||
mustMatch: false, |
||||
extraParams: {}, |
||||
selectFirst: true, |
||||
formatItem: function(row) { return row[0]; }, |
||||
formatMatch: null, |
||||
autoFill: false, |
||||
width: 0, |
||||
multiple: false, |
||||
multipleSeparator: ", ", |
||||
highlight: function(value, term) { |
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); |
||||
}, |
||||
scroll: true, |
||||
scrollHeight: 180 |
||||
}; |
||||
|
||||
$.Autocompleter.Cache = function(options) { |
||||
|
||||
var data = {}; |
||||
var length = 0; |
||||
|
||||
function matchSubset(s, sub) { |
||||
if (!options.matchCase) |
||||
s = s.toLowerCase(); |
||||
var i = s.indexOf(sub); |
||||
if (options.matchContains == "word"){ |
||||
i = s.toLowerCase().search("\\b" + sub.toLowerCase()); |
||||
} |
||||
if (i == -1) return false; |
||||
return i == 0 || options.matchContains; |
||||
}; |
||||
|
||||
function add(q, value) { |
||||
if (length > options.cacheLength){ |
||||
flush(); |
||||
} |
||||
if (!data[q]){ |
||||
length++; |
||||
} |
||||
data[q] = value; |
||||
} |
||||
|
||||
function populate(){ |
||||
if( !options.data ) return false; |
||||
// track the matches
|
||||
var stMatchSets = {}, |
||||
nullData = 0; |
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1; |
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = []; |
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) { |
||||
var rawValue = options.data[i]; |
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; |
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length); |
||||
if ( value === false ) |
||||
continue; |
||||
|
||||
var firstChar = value.charAt(0).toLowerCase(); |
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] ) |
||||
stMatchSets[firstChar] = []; |
||||
|
||||
// if the match is a string
|
||||
var row = { |
||||
value: value, |
||||
data: rawValue, |
||||
result: options.formatResult && options.formatResult(rawValue) || value |
||||
}; |
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row); |
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) { |
||||
stMatchSets[""].push(row); |
||||
} |
||||
}; |
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) { |
||||
// increase the cache size
|
||||
options.cacheLength++; |
||||
// add to the cache
|
||||
add(i, value); |
||||
}); |
||||
} |
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25); |
||||
|
||||
function flush(){ |
||||
data = {}; |
||||
length = 0; |
||||
} |
||||
|
||||
return { |
||||
flush: flush, |
||||
add: add, |
||||
populate: populate, |
||||
load: function(q) { |
||||
if (!options.cacheLength || !length) |
||||
return null; |
||||
/* |
||||
* if dealing w/local data and matchContains than we must make sure |
||||
* to loop through all the data collections looking for matches |
||||
*/ |
||||
if( !options.url && options.matchContains ){ |
||||
// track all matches
|
||||
var csub = []; |
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){ |
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){ |
||||
var c = data[k]; |
||||
$.each(c, function(i, x) { |
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) { |
||||
csub.push(x); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
return csub; |
||||
} else |
||||
// if the exact item exists, use it
|
||||
if (data[q]){ |
||||
return data[q]; |
||||
} else |
||||
if (options.matchSubset) { |
||||
for (var i = q.length - 1; i >= options.minChars; i--) { |
||||
var c = data[q.substr(0, i)]; |
||||
if (c) { |
||||
var csub = []; |
||||
$.each(c, function(i, x) { |
||||
if (matchSubset(x.value, q)) { |
||||
csub[csub.length] = x; |
||||
} |
||||
}); |
||||
return csub; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) { |
||||
var CLASSES = { |
||||
ACTIVE: "ac_over" |
||||
}; |
||||
|
||||
var listItems, |
||||
active = -1, |
||||
data, |
||||
term = "", |
||||
needsInit = true, |
||||
element, |
||||
list; |
||||
|
||||
// Create results
|
||||
function init() { |
||||
if (!needsInit) |
||||
return; |
||||
element = $("<div/>") |
||||
.hide() |
||||
.addClass(options.resultsClass) |
||||
.css("position", "absolute") |
||||
.appendTo(document.body); |
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) { |
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { |
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); |
||||
$(target(event)).addClass(CLASSES.ACTIVE); |
||||
} |
||||
}).click(function(event) { |
||||
$(target(event)).addClass(CLASSES.ACTIVE); |
||||
select(); |
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus(); |
||||
return false; |
||||
}).mousedown(function() { |
||||
config.mouseDownOnSelect = true; |
||||
}).mouseup(function() { |
||||
config.mouseDownOnSelect = false; |
||||
}); |
||||
|
||||
if( options.width > 0 ) |
||||
element.css("width", options.width); |
||||
|
||||
needsInit = false; |
||||
} |
||||
|
||||
function target(event) { |
||||
var element = event.target; |
||||
while(element && element.tagName != "LI") |
||||
element = element.parentNode; |
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element) |
||||
return []; |
||||
return element; |
||||
} |
||||
|
||||
function moveSelect(step) { |
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); |
||||
movePosition(step); |
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); |
||||
if(options.scroll) { |
||||
var offset = 0; |
||||
listItems.slice(0, active).each(function() { |
||||
offset += this.offsetHeight; |
||||
}); |
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { |
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); |
||||
} else if(offset < list.scrollTop()) { |
||||
list.scrollTop(offset); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
function movePosition(step) { |
||||
active += step; |
||||
if (active < 0) { |
||||
active = listItems.size() - 1; |
||||
} else if (active >= listItems.size()) { |
||||
active = 0; |
||||
} |
||||
} |
||||
|
||||
function limitNumberOfItems(available) { |
||||
return options.max && options.max < available |
||||
? options.max |
||||
: available; |
||||
} |
||||
|
||||
function fillList() { |
||||
list.empty(); |
||||
var max = limitNumberOfItems(data.length); |
||||
for (var i=0; i < max; i++) { |
||||
if (!data[i]) |
||||
continue; |
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); |
||||
if ( formatted === false ) |
||||
continue; |
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; |
||||
$.data(li, "ac_data", data[i]); |
||||
} |
||||