Index: main/plugin-sms/plugin.xml
===================================================================
--- main/plugin-sms/plugin.xml	(revision 30961)
+++ main/plugin-sms/plugin.xml	(working copy)
@@ -229,6 +229,67 @@
                 </action>                      
                 <right context-prefix="/contributor">Plugins_SMS_Right_Action</right>              
             </extension>
+            
+            <extension id="org.ametys.sms.ImportSubscribersButton"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
+                    <param name="action">org.ametys.sms.subscribers.SubscribersActions.importCsv</param>
+                    <param name="target-type">^sms-list$</param>
+                    <param name="label" i18n="true">PLUGINS_SMS_IMPORT_SUBSCRIBERS_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_SMS_IMPORT_SUBSCRIBERS_DESC</param>
+                    <param name="disabled-description" i18n="true">PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_DISABLED</param>
+                    <param name="footer" i18n="true">PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_FOOTER</param>
+                    <param name="icon-small" file="true">img/import_csv_16.png</param>
+                    <param name="icon-medium" file="true">img/import_csv_32.png</param>
+                    <param name="icon-large" file="true">img/import_csv_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/sms/subscribers/SubscribersActions.i18n.js</file>
+                </scripts>
+                <right>Plugins_Newsletter_Right_HandleSubscribers</right>   
+            </extension>
+            
+            <extension id="org.ametys.sms.AddNumberButton"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
+                    <param name="action">org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber</param>
+                    <param name="target-type">^sms-list$</param>
+                    <param name="label" i18n="true">PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_DESC</param>
+                    <param name="disabled-description" i18n="true">PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_DISABLED</param>
+                    <param name="footer" i18n="true">PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_FOOTER</param>
+                    <param name="icon-small" file="true">img/import_csv_16.png</param>
+                    <param name="icon-medium" file="true">img/import_csv_32.png</param>
+                    <param name="icon-large" file="true">img/import_csv_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/sms/subscribers/SubscribersActions.i18n.js</file>
+                </scripts>
+                <right>Plugins_Newsletter_Right_HandleSubscribers</right>   
+            </extension>
+            
+            <extension id="org.ametys.sms.DeleteNumbersButton"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
+                    <param name="action">org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers</param>
+                    <param name="enable-multiselection">true</param>
+                    <param name="target-type">^sms-list-phone-number$</param>
+                    <param name="label" i18n="true">PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DESC</param>
+                    <param name="disabled-description" i18n="true">PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DISABLED</param>
+                    <param name="footer" i18n="true">PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_FOOTER</param>
+                    <param name="icon-small" file="true">img/import_csv_16.png</param>
+                    <param name="icon-medium" file="true">img/import_csv_32.png</param>
+                    <param name="icon-large" file="true">img/import_csv_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/sms/subscribers/SubscribersActions.i18n.js</file>
+                </scripts>
+                <right>Plugins_Newsletter_Right_HandleSubscribers</right>   
+            </extension>
 
             <extension id="org.ametys.sms.PhoneNumbersFactory"
                        point="org.ametys.cms.workspace.uitool.UIToolsFactoriesManager"
@@ -286,7 +347,7 @@
                        point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
                        class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
                 <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
-                    <param name="target-type">^sms-list-provider$</param>
+                    <param name="target-type">^sms-list$</param>
                     <param name="action">org.ametys.sms.list.Edit.edit</param>
                     <param name="enable-multiselection">false</param>                    
                     <param name="label" i18n="true">PLUGINS_SMS_LIST_UITOOL_SMS_EDIT_LABEL</param>
@@ -306,7 +367,7 @@
                        point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
                        class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
                 <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
-                    <param name="target-type">^sms-list-provider$</param>
+                    <param name="target-type">^sms-list$</param>
                     <param name="action">org.ametys.sms.list.Delete.act</param>
                     <param name="label" i18n="true">PLUGINS_SMS_LIST_UITOOL_SMS_DELETE_LABEL</param>
                     <param name="default-description" i18n="true">PLUGINS_SMS_LIST_UITOOL_SMS_DELETE_DESC</param>
@@ -423,5 +484,4 @@
 			</extension>	
 		</extensions>
 	</feature>
-    
 </plugin>
Index: main/plugin-sms/sitemap.xmap
===================================================================
--- main/plugin-sms/sitemap.xmap	(revision 30961)
+++ main/plugin-sms/sitemap.xmap	(working copy)
@@ -33,6 +33,9 @@
             <map:action name="stop-sms" src="org.ametys.plugins.sms.stop.UnsubscribeAction"/>
             <map:action name="add-edit-list" src="org.ametys.plugins.sms.action.AddOrEditListAction"/>
             <map:action name="delete-list" src="org.ametys.plugins.sms.action.DeleteListAction"/>
+            <map:action name="import-subscribers" src="org.ametys.plugins.sms.action.ImportSMSListSubscribersAction"/>
+            <map:action name="delete-phone-numbers" src="org.ametys.plugins.sms.action.DeletePhoneNumbersAction"/>
+            <map:action name="add-phone-number" src="org.ametys.plugins.sms.action.AddPhoneNumberAction"/>
         </map:actions>
     </map:components>
     
@@ -121,7 +124,37 @@
                 <map:serialize type="xml"/> 
             </map:match>
             
+            <map:match pattern="subscribers/import">
+                <map:act type="import-subscribers">
+                    <map:generate type="action-result">
+                        <map:parameter name="error" value="{error}"/>
+                        <map:parameter name="subscribedCount" value="{subscribedCount}"/>
+                        <map:parameter name="errorCount" value="{errorCount}"/>
+                        <map:parameter name="existingCount" value="{existingCount}"/>
+                        <map:parameter name="success" value="{success}"/>
+                        <map:parameter name="smsListId" value="{smsListId}"/>
+                    </map:generate>
+                    <map:transform src="pages/import/import-result-json.xsl" type="xslt"/>
+                    <map:serialize type="text" mime-type="text/html"/>
+                </map:act>
+            </map:match>
+            
+            <map:match pattern="subscribers/add-phone-number">
+                <map:act type="add-phone-number">
+                    <map:generate type="action-result" >
+                        <map:parameter name="error" value="{error}"/>
+                     </map:generate>
+                    <map:serialize type="xml"/>
+                </map:act>
+            </map:match>
+            
+            <map:match pattern="subscribers/delete-phone-numbers">
+                <map:act type="delete-phone-numbers">
+                    <map:generate type="action-result" />
+                    <map:serialize type="xml"/>
+                </map:act>
+            </map:match>
+            
         </map:pipeline>
     </map:pipelines>
-    
 </map:sitemap>
Index: main/plugin-sms/i18n/messages_fr.xml
===================================================================
--- main/plugin-sms/i18n/messages_fr.xml	(revision 30961)
+++ main/plugin-sms/i18n/messages_fr.xml	(working copy)
@@ -79,6 +79,64 @@
     <message key="PLUGINS_SMS_LIST_NO_RIGHT_DESC">Ce bouton est désactivé car vous n'avez pas les droits nécessaires.</message>
     <message key="PLUGINS_SMS_LIST_WAIT_DESC">Veuillez patienter...</message>
     
+    <!-- Import Subscribers -->
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_LABEL">Importer des abonnés</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DESC">Importer des abonnés depuis un fichier texte.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_DISABLED">Ce bouton est désactivé car aucune liste de diffusion n'est sélectionnée.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_FOOTER">Voir l'aide pour plus de détails</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_HINT">Veuillez sélectionner un fichier .txt ou .csv de votre disque dur contenant les numéros à importer (un numéro par ligne).</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_LABEL">Fichier</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_DESC">Fichier texte contenant les numéros à importer</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_EMPTY_TEXT">Veuillez sélectionner un fichier texte</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_BUTTON">Parcourir</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_CANCEL">Annuler</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS">Supprimer les abonnés existants</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS_DESC">Cochez cette case pour vider la base des abonnés avant d'importer le fichier</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_TITLE">Importer des abonnés depuis un fichier texte</message>
+    <message key="PLUGINS_SMS_IMPORT_DIALOG_WAIT_TITLE">Import des abonnés</message>
+    <message key="PLUGINS_SMS_IMPORT_DIALOG_WAIT_MSG">Import des abonnés en cours...</message>
+    <message key="PLUGINS_SMS_IMPORT_CLEAN_CONFIRM">Voulez-vous vraiment supprimer les abonnements existants avant d'importer les numéros du fichier ?</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_TITLE">Import terminé avec succès</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_START">L'import est terminé :&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_SINGLE">numéro a été importé avec succès.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_MULTIPLE">numéros ont été importés avec succès.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_SINGLE">numéro n'a pas été importé car il était déjà présent dans la liste de diffusion.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_MULTIPLE">numéros n'ont pas été importés car ils étaient déjà présents.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_ERROR_SINGLE">numéro n'a pas été importé car il était non-conforme.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_ERROR_MULTIPLE">numéros n'ont pas été importés car ils étaient non-conformes.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_TITLE">Import des abonnés</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_UNKNOWN_CATEGORY">Les numéros n'ont pas pu être importés : la liste de diffusion n'existe pas.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_INVALID_FILE">Vous devez sélectionner un fichier au format .txt ou .csv.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_REJECTED_FILE">Le fichier est trop volumineux. Les numéros n'ont pas pu être importés.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR">Une erreur inattendue est survenue : Les numéros n'ont pas pu être importés.</message>
+    <message key="PLUGINS_SMS_IMPORT_GET_SUBSCRIBERS_ERROR">Une erreur est survenue: la liste des abonnés n'a pu être récupérée.</message>
+    
+    <!-- Add phone numbers -->
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_LABEL">Ajouter un abonné</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_DESC">Ajoute un abonné à la liste de diffusion sélectionnée.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_DISABLED">Ce bouton est désactivé parce que vous n'avez pas les droits suffisants.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_BUTTON_FOOTER">Voir l'aide pour plus de détails.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_TITLE">Ajouter un numéro à une liste de diffusion</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_HINT">Entrez le numéro que vous souhaitez ajouter à la liste de diffusion sélectionnée.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_NUMBER_FIELD_LABEL">Numéro</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_NUMBER_FIELD_DESC">Numéro dans un des formats acceptés</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_BUTTON_CANCEL">Annuler</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR">Echec de l'ajout.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_ALREADY_EXISTS">Ce numéro fait déjà partie de la liste de diffusion sélectionnée.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_WRONG_FORMAT">Le format du numéro entré ne fait pas partie des formats autorisés.</message>
+    
+    <!-- Delete phone numbers -->
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_LABEL">Supprimer des abonnés</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DESC">Supprime les numéros sélectionnés dans l'outil  \"Liste des numéros\".</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DISABLED">Ce bouton est désactivé soit parce que vous n'avez pas les droits suffisants, soit parce qu'aucun numéro de téléphone n'est sélectionné.</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_FOOTER">Voir l'aide pour plus de détails.</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TITLE">Confirmer la suppression</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TEXT">Etes-vous sûr de vouloir supprimer ce(s) numéro(s) de téléphone de la liste de diffusion ?</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_ERROR">Echec de la suppression.</message>
+    
+    
     <!--  +
           | BUS
           + -->
@@ -101,7 +159,7 @@
     <message key="PLUGINS_SMS_ADD_LIST_FORM_HINT">Veuillez remplir les informations suivantes pour créer ou éditer une liste de diffusion SMS.</message>
     <message key="PLUGINS_SMS_ADD_LIST_FORM_BOX_TITLE">Nouvelle liste</message>    
     <message key="PLUGINS_SMS_EDIT_LIST_FORM_BOX_TITLE">Modifier</message>    
-    <message key="PLUGINS_SMS_ADD_LIST_DELETE_ERROR">La suppression a échouée</message>
+    <message key="PLUGINS_SMS_ADD_LIST_DELETE_ERROR">La suppression a échoué</message>
     <message key="PLUGINS_SMS_ADD_LIST_HANDLE_LIST_DELETE">Supprimer la liste</message>
     <message key="PLUGINS_SMS_ADD_LIST_HANDLE_LIST_DELETE_CONFIRM">Etes-vous sûr de vouloir supprimer cette liste ?</message>
     <message key="PLUGINS_SMS_ADD_LIST_ADD_EDIT_ERROR">L'ajout ou l'édition a échoué.</message>
@@ -142,7 +200,7 @@
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_DIRECTORY">La liste de diffusion n'est pas définie</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_CAPTCHA">Le texte de l'image est invalide</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_PHONE">Le numéro de téléphone est invalide</message>
-    <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_PHONE2">Le numéro de téléphone enregistré ne correspond pas au format internationnal</message>
+    <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_PHONE2">Le numéro de téléphone enregistré ne correspond pas au format international</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_REGISTER">Le type du formulaire n'est pas défini</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_ALREADY_RECORD">Vous êtes déjà inscrit à cette liste de diffusion.</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_DELETE">Votre désabonnement à la liste de diffusion a bien été pris en compte.</message>
@@ -154,7 +212,7 @@
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_BACK_ALT_SUCCESS">Utilisez le bouton 'Précédent' de votre navigateur web pour revenir à la page précédente.</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_BACK_ALT">Utilisez le bouton 'Précédent' de votre navigateur web pour corriger votre formulaire d'inscription/désinscription et rafraichissez l'image.</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_RECORD_TITLE">Inscription réussie</message>
-    <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_DELETED_TITLE">Supression réussie</message>
+    <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_DELETED_TITLE">Suppression réussie</message>
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_MESSAGE_ISSUE_TITLE">Une erreur est survenue</message>
 	<message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_NO_LIST">Il n'existe actuellement aucune liste de diffusion sur le site</message>
 	<message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_XSLT_LABEL">Affichage</message>
Index: main/plugin-sms/i18n/messages_en.xml
===================================================================
--- main/plugin-sms/i18n/messages_en.xml	(revision 30961)
+++ main/plugin-sms/i18n/messages_en.xml	(working copy)
@@ -76,9 +76,68 @@
     <message key="PLUGINS_SMS_LIST_UITOOL_SMS_DELETE_DESC">Delete the selected list</message>
     <message key="PLUGINS_SMS_LIST_LISTACTION_DISABLED">This button is disabled because no list is selected.</message>
     <message key="PLUGINS_SMS_LIST_LISTACTION_NOEDITABLE">This button is disabled because the the selected list can not be modified.</message>
-    <message key="PLUGINS_SMS_LIST_NO_RIGHT_DESC">This button is disabled because you haven't the convenient rights.</message>
+    <message key="PLUGINS_SMS_LIST_NO_RIGHT_DESC">This button is disabled because you don't have the appropriate rights.</message>
     <message key="PLUGINS_SMS_LIST_WAIT_DESC">Please wait...</message>
     
+    <!-- Import Subscribers -->
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_LABEL">Import subscribers</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DESC">Import subscribers from a text file.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_DISABLED">This button is disabled because you don't have the appropriate rights.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_BUTTON_FOOTER">See help for more details</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_HINT">Please select a local .txt or .csv file containing the phone numbers to import (1 phone number per line).</message> 
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_LABEL">File</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_DESC">Text file containing the phone numbers to import</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_EMPTY_TEXT">Please select a text file</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_BUTTON">Browse</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_CANCEL">Cancel</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS">Delete existing subscribers</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS_DESC">Check this bow to empty the selected list's subscribers database before importing the phone numbers from the file</message>
+    <message key="PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_TITLE">Import subscribers from a text file</message>
+    <message key="PLUGINS_SMS_IMPORT_DIALOG_WAIT_TITLE">Import subscribers</message>
+    <message key="PLUGINS_SMS_IMPORT_DIALOG_WAIT_MSG">Importing the subscribers...</message>
+    <message key="PLUGINS_SMS_IMPORT_CLEAN_CONFIRM">Do you really wish to delete the existing subscribers before importing the phone numbers from the file ?</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_TITLE">Import successful</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_START">The import has completed :&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_SINGLE">number has been successfully imported.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_MULTIPLE">numbers have been successfully imported.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_SINGLE">number was not imported because it was already in the broadcasting list.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_MULTIPLE">numbers were not imported because they were already in the broadcasting list.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_ERROR_SINGLE">number was not imported because it didn't have the appropriate format.</message>
+    <message key="PLUGINS_SMS_IMPORT_SUCCESS_ERROR_MULTIPLE">numbers were not imported because they didn't have the appropriate format.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_TITLE">Subscribers import</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_UNKNOWN_CATEGORY">The phone numbers haven't been imported : the broadcasting list does not exist.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_INVALID_FILE">You have to select either a .csv or .txt file</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR_REJECTED_FILE">The file is too voluminous. The phone numbers have not been imported.</message>
+    <message key="PLUGINS_SMS_IMPORT_ERROR">An unexpected error occurred: The phone numbers could not be imported.</message>
+   <message key="PLUGINS_SMS_IMPORT_GET_SUBSCRIBERS_ERROR">An error occurred: the subscribers' list could not be retrieved</message> 
+    
+    <!-- Add phone number -->
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_BUTTON_LABEL">Add a subscriber</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_BUTTON_DESC">Add a subscriber to the selected boradcasting liste.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_BUTTON_DISABLED">This button is deactivated because you don't have sufficient rights.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_BUTTON_FOOTER">See help for more details.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_TITLE">Add a number to a broadcasting list</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_DIALOG_HINT">Type in the number you wish to add to the selected broadcasting list.&lt;br/&gt;</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_DIALOG_NUMBER_FIELD_LABEL">Number</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBERS_DIALOG_NUMBER_FIELD_DESC">Number in one of the accepted formats.</message>
+    <message key="PLUGINS_SMS_SEND_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_SMS_SEND_DIALOG_BUTTON_CANCEL">Cancel</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_WAIT_TITLE">Add phone number</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_WAIT_MSG">Adding phone number...</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR">The addition failed.</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_ALREADY_EXISTS">This number already exists in the selected broadcasting list</message>
+    <message key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_WRONG_FORMAT">The format of the number is not part of the accepted formats</message>
+    
+    <!-- Delete phone numbers -->
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_LABEL">Delete</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DESC">Delete the selected phone numbers of the \"Phone numbers list\" tool.</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_DISABLED">This button is deactivated either because you don't have sufficient rights or because no phone number is currently selected.</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_BUTTON_FOOTER">See help for more details.</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TITLE">Confirm deletion</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TEXT">Do you really want to delete the selected phone number(s) ?</message>
+    <message key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_ERROR">The deletion has failed.</message>
+        
      <!-- +
           | BUS
           + -->
@@ -112,6 +171,7 @@
     <message key="RIBBON_TABS_TAB_SMS_GROUP_LIST_LABEL">Lists</message>
     <message key="RIBBON_TABS_TAB_SMS_GROUP_SUBSCRIBERS_LABEL">Subscribers</message>
     <message key="RIBBON_TABS_TAB_SMS_LABEL">SMS lists</message>
+    <message key="RIBBON_TABS_TAB_SMS_GROUP_PHONE_NUMBERS_LABEL">Phone numbers</message>
     
     <!--  Subscribtion Service -->
     <message key="PLUGINS_SMS_SUBSCRIPTION_SERVICE_LABEL">Subscribers SMS</message>
Index: main/plugin-sms/src/org/ametys/plugins/sms/dao/SubscribeDAO.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/dao/SubscribeDAO.java	(revision 30961)
+++ main/plugin-sms/src/org/ametys/plugins/sms/dao/SubscribeDAO.java	(working copy)
@@ -40,7 +40,7 @@
     public static final String ROLE = SubscribeDAO.class.getName(); 
 
     /**
-     * Check if the phoneNumber exist in the database
+     * Check if the phoneNumber exists in the database
      * @param phoneNumber
      * @param list
      * @return true if exist
Index: main/plugin-sms/src/org/ametys/plugins/sms/SMSHelper.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/SMSHelper.java	(revision 0)
+++ main/plugin-sms/src/org/ametys/plugins/sms/SMSHelper.java	(revision 0)
@@ -0,0 +1,94 @@
+package org.ametys.plugins.sms;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+
+import org.ametys.runtime.config.Config;
+
+/**
+ * Static helper gathering mutual operations of the SMS plugin
+ */
+public final class SMSHelper
+{
+    /** The pattern for an international phone number */
+    public static final Pattern PHONE_NUMBER_INTERNATIONAL_VALIDATOR = Pattern.compile("^\\+[0-9]{6,}$");
+
+    /**
+     * Transform the phone number to the international format defined by the admin
+     * @param phoneNumber the phone number to transform
+     * @return the transformed phone number
+     */
+    public static String transformPhoneNumber(String phoneNumber)
+    {
+        String regexConvert = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.convert");
+        String[] tabRegexConvert = StringUtils.split(regexConvert, "\n");
+        String newPhoneNumber = phoneNumber;
+
+        for (int i = 0; i < tabRegexConvert.length; i++)
+        {
+            String[] regexParams = StringUtils.split(tabRegexConvert[i], "/");
+            String regex = regexParams[0];
+            String replaceExpr = regexParams[1];
+            
+            // ensure regexp takes the whole expression
+            if (!regex.startsWith("^"))
+            {
+                regex = "^" + regex;
+            }
+            if (!regex.endsWith("$"))
+            {
+                regex = regex + "$";
+            }
+             
+            // We transform the phone number to an international phone number
+            Matcher m = Pattern.compile(regex).matcher(phoneNumber);
+            if (m.matches())
+            {
+                newPhoneNumber = replaceExpr;
+                
+                for (int c = 1; c <= m.groupCount(); c++)
+                {
+                    newPhoneNumber = newPhoneNumber.replaceAll("\\{" + c + "\\}", m.group(c));
+                }
+                break;
+            }
+        }
+        
+        return newPhoneNumber;
+    }
+    
+    /**
+     * Check if the phoneNumber is in the international format defined by the admin
+     * @param phoneNumber
+     * @return <code>true</code> if the phone number matches the format,<code>false</code> otherwise
+     */
+    public static boolean checkPhoneNumber(String phoneNumber)
+    {
+        String regexPhone = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.autorized");         
+        String[] tabRegexPhone = StringUtils.split(regexPhone, "\n");
+
+        for (String regex : tabRegexPhone)
+        {
+            // ensure regexp takes the whole expression
+            String fullRegex = regex;
+            if (!regex.startsWith("^"))
+            {
+                fullRegex = "^" + fullRegex;
+            }
+            if (!regex.endsWith("$"))
+            {
+                fullRegex = fullRegex + "$";
+            }
+            
+            // We check if the phoneNumber format is correct
+            if (Pattern.compile(fullRegex).matcher(phoneNumber).matches())
+            {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+}
Index: main/plugin-sms/src/org/ametys/plugins/sms/action/DeletePhoneNumbersAction.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/action/DeletePhoneNumbersAction.java	(revision 0)
+++ main/plugin-sms/src/org/ametys/plugins/sms/action/DeletePhoneNumbersAction.java	(revision 0)
@@ -0,0 +1,59 @@
+package org.ametys.plugins.sms.action;
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+
+import org.ametys.plugins.sms.dao.SubscribeDAO;
+
+/**
+ * Action deleting the sql entries for the selected phone numbers of a given sms list
+ */
+public class DeletePhoneNumbersAction extends ServiceableAction
+{
+    /** The subscribe DAO */
+    protected SubscribeDAO _subscribeDAO;
+    
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);  
+        _subscribeDAO = (SubscribeDAO) serviceManager.lookup(SubscribeDAO.ROLE);
+    }
+
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {   
+        Request request = ObjectModelHelper.getRequest(objectModel); 
+        String[] phoneNumbers = request.getParameterValues("phoneNumbers");
+        String smsListId = request.getParameter("smsListId");
+
+        for (String phoneNumber : phoneNumbers)
+        {
+            _subscribeDAO.deleteNumber(phoneNumber, smsListId);
+        }
+        
+        return EMPTY_MAP;
+    } 
+}
Index: main/plugin-sms/src/org/ametys/plugins/sms/action/ImportSMSListSubscribersAction.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/action/ImportSMSListSubscribersAction.java	(revision 0)
+++ main/plugin-sms/src/org/ametys/plugins/sms/action/ImportSMSListSubscribersAction.java	(revision 0)
@@ -0,0 +1,214 @@
+package org.ametys.plugins.sms.action;
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.servlet.multipart.Part;
+import org.apache.cocoon.servlet.multipart.PartOnDisk;
+import org.apache.cocoon.servlet.multipart.RejectedPart;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+
+import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.sms.SMSHelper;
+import org.ametys.plugins.sms.dao.SubscribeDAO;
+import org.ametys.runtime.util.BOMInputStream;
+
+/**
+ * Action importing subscribers from a CSV file supposedly containing one valid number per line
+ */
+public class ImportSMSListSubscribersAction extends ServiceableAction
+{
+    private static final String[] __ALLOWED_EXTENSIONS = new String[] {"txt", "csv"};
+    
+    /** The subscribers DAO. */
+    private SubscribeDAO _subscribersDAO;
+    
+    /** The Ametys object resolver */
+    private AmetysObjectResolver _ametysObjectResolver;
+
+    @Override
+    public void service(ServiceManager smanager) throws ServiceException
+    {
+        super.service(smanager);
+        _subscribersDAO = (SubscribeDAO) smanager.lookup(SubscribeDAO.ROLE);
+        _ametysObjectResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
+    }
+    
+    @Override
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {
+        Map<String, String> result = new HashMap<String, String>();
+
+        Request request = ObjectModelHelper.getRequest(objectModel);
+
+        String smsListId = request.getParameter("smsListId");
+        boolean cleanTable = "true".equals(request.getParameter("cleanSubscribers"));
+        
+        Part part = (Part) request.get("importFile");
+        if (part instanceof RejectedPart)
+        {
+            return Collections.singletonMap("error", "rejected-file");
+        }
+        
+        PartOnDisk uploadedFilePart = (PartOnDisk) part;
+        File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null;
+        String filename = (uploadedFilePart != null) ? uploadedFilePart.getFileName().toLowerCase() : null;
+        
+        if (!FilenameUtils.isExtension(filename, __ALLOWED_EXTENSIONS))
+        {
+            return Collections.singletonMap("error", "invalid-extension");
+        }
+        
+        // Test if the sms list exists.
+        if (!_ametysObjectResolver.hasAmetysObjectForId(smsListId))
+        {
+            return Collections.singletonMap("error", "unknown-list");
+        }
+        
+        FileInputStream fileIS =  null;
+        BOMInputStream bomIS = null;
+        Collection<String> phoneNumbers = null;
+        try
+        {
+            fileIS = new FileInputStream(uploadedFile);
+            bomIS = new BOMInputStream(fileIS);
+            
+            // Extract the phone numbers from the file.
+            phoneNumbers = _getPhoneNumbers(bomIS, result);
+        }
+        finally
+        {
+            IOUtils.closeQuietly(bomIS);
+            IOUtils.closeQuietly(fileIS);
+        }
+        
+        if (!phoneNumbers.isEmpty())
+        {
+            // Empty the broadcasting list subscribers if needed.
+            if (cleanTable)
+            {
+                _subscribersDAO.deleteAllNumbers(smsListId);
+            }
+            
+            // Insert the phone numbers.
+            _insertSubscribers(phoneNumbers, smsListId, result);
+        }
+        else
+        {
+            result.put("subscribedCount", "0");
+            result.put("existingCount", "0");
+            result.put("errorCount", "0");
+        }
+        
+        result.put("success", "true");
+        result.put("smsListId", smsListId);
+        
+        return result;
+    }
+    
+    /**
+     * Extract the phone numbers from the file.
+     * @param bomIS the BOM input stream 
+     * @return the collection of phone numbers.
+     * @throws IOException if an error occurs reading the file.
+     */
+    private Set<String> _getPhoneNumbers(BOMInputStream bomIS, Map<String, String> result) throws IOException
+    {
+        Set<String> phoneNumbers = new HashSet<String>();
+        int errorCount = 0;
+        
+        for (String phoneNumber : (List<String>) IOUtils.readLines(bomIS))
+        {
+            // We check if the phone number is correct
+            phoneNumber = phoneNumber.trim();           
+            phoneNumber = SMSHelper.transformPhoneNumber(phoneNumber);
+            
+            if (!SMSHelper.checkPhoneNumber(phoneNumber) && !SMSHelper.PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
+            {   
+                getLogger().warn("Import subscribers phone number: '" + phoneNumber + "' is not a valid phone number; it will be ignored");
+                errorCount++;
+            }
+            else
+            {
+                phoneNumbers.add(phoneNumber);
+            }
+        }
+        result.put("errorCount", Integer.toString(errorCount));
+        return phoneNumbers;
+    }
+    
+    /**
+     * Insert subscribers in the broadcasting list
+     * @param phoneNumbers the list of phone numbers to insert
+     * @param smsListId the id of the sms list 
+     * @param result the server call result 
+     */
+    private void _insertSubscribers(Collection<String> phoneNumbers, String smsListId, Map<String, String> result)
+    {
+        int subscribedCount = 0;
+        int existingCount = 0;
+        int errorCount = Integer.parseInt(result.get("errorCount"));
+        
+        for (String phoneNumber : phoneNumbers)
+        {
+            try
+            {
+                if (_subscribersDAO.numberAlreadyExists(phoneNumber, smsListId))
+                {
+                    existingCount++;
+                }
+                else
+                {
+                    _subscribersDAO.insertNumber(phoneNumber, smsListId);
+                    
+                    if (getLogger().isInfoEnabled())
+                    {
+                        getLogger().info("The user with phoneNumber '" + phoneNumber + "' subscribed to the list '" + smsListId + "'.");
+                    }
+                    
+                    subscribedCount++;
+                }
+            }
+            catch (Exception e)
+            {
+                getLogger().error("Impossible to add as a subscriber the phoneNumber " + phoneNumber + " in list '" + smsListId + "'.", e);
+                errorCount++;
+            }
+        }
+        
+        result.put("subscribedCount", Integer.toString(subscribedCount));
+        result.put("existingCount", Integer.toString(existingCount));
+        result.put("errorCount", Integer.toString(errorCount));
+    }
+}
Index: main/plugin-sms/src/org/ametys/plugins/sms/action/AddPhoneNumberAction.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/action/AddPhoneNumberAction.java	(revision 0)
+++ main/plugin-sms/src/org/ametys/plugins/sms/action/AddPhoneNumberAction.java	(revision 0)
@@ -0,0 +1,73 @@
+package org.ametys.plugins.sms.action;
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+
+import org.ametys.plugins.sms.SMSHelper;
+import org.ametys.plugins.sms.dao.SubscribeDAO;
+
+/** 
+ * Add a phone number to a selected broadcasting list
+ */
+public class AddPhoneNumberAction extends ServiceableAction
+{
+    /** The subscribe DAO */
+    protected SubscribeDAO _subscribeDAO;
+    
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);  
+        _subscribeDAO = (SubscribeDAO) serviceManager.lookup(SubscribeDAO.ROLE);
+    }
+    
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {
+        Map<String, Object> result = new HashMap<String, Object> ();
+        
+        Request request = ObjectModelHelper.getRequest(objectModel);
+        String smsListId = request.getParameter("smsListId");
+        String phoneNumber = request.getParameter("phoneNumber");
+        
+        phoneNumber = phoneNumber.trim();           
+        phoneNumber = SMSHelper.transformPhoneNumber(phoneNumber);
+
+        if (!SMSHelper.checkPhoneNumber(phoneNumber) || !SMSHelper.PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
+        {   
+            result.put("error", "wrong-format");
+        }
+        else if (_subscribeDAO.numberAlreadyExists(phoneNumber, smsListId))
+        {
+            result.put("error", "already-exists");
+        }
+        else
+        {
+            _subscribeDAO.insertNumber(phoneNumber, smsListId);
+        }
+        
+        return result;
+    }
+}
Index: main/plugin-sms/src/org/ametys/plugins/sms/stop/UnsubscribeAction.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/stop/UnsubscribeAction.java	(revision 30961)
+++ main/plugin-sms/src/org/ametys/plugins/sms/stop/UnsubscribeAction.java	(working copy)
@@ -16,7 +16,6 @@
 package org.ametys.plugins.sms.stop;
 
 import java.util.Map;
-import java.util.regex.Pattern;
 
 import org.apache.avalon.framework.parameters.Parameters;
 import org.apache.avalon.framework.service.ServiceException;
@@ -25,6 +24,7 @@
 import org.apache.cocoon.environment.Redirector;
 import org.apache.cocoon.environment.SourceResolver;
 
+import org.ametys.plugins.sms.SMSHelper;
 import org.ametys.plugins.sms.broker.Broker;
 import org.ametys.plugins.sms.dao.SubscribeDAO;
 
@@ -33,9 +33,6 @@
  */
 public class UnsubscribeAction extends ServiceableAction
 {
-    /** The pattern for international phone numbers */
-    private static final Pattern __PHONE_NUMBER_INTERNATIONAL_VALIDATOR = Pattern.compile("^\\+[0-9]{6,}$");
-    
     /** The subscribe DAO */
     private SubscribeDAO _subscribeDAO;
 
@@ -55,7 +52,7 @@
         String phoneNumber = _broker.getPhoneNumberFromStopRequest();
         if (phoneNumber != null)
         {
-            if (!__PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
+            if (!SMSHelper.PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
             {   
                 throw new IllegalArgumentException("The phone number [" + phoneNumber + "] is invalid.");
             }
Index: main/plugin-sms/src/org/ametys/plugins/sms/service/SubscribeAction.java
===================================================================
--- main/plugin-sms/src/org/ametys/plugins/sms/service/SubscribeAction.java	(revision 30961)
+++ main/plugin-sms/src/org/ametys/plugins/sms/service/SubscribeAction.java	(working copy)
@@ -20,8 +20,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.apache.avalon.framework.parameters.Parameters;
 import org.apache.avalon.framework.service.ServiceException;
@@ -31,12 +29,11 @@
 import org.apache.cocoon.environment.Redirector;
 import org.apache.cocoon.environment.Request;
 import org.apache.cocoon.environment.SourceResolver;
-import org.apache.commons.lang.StringUtils;
 
 import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.sms.SMSHelper;
 import org.ametys.plugins.sms.dao.SubscribeDAO;
 import org.ametys.runtime.captcha.CaptchaHelper;
-import org.ametys.runtime.config.Config;
 import org.ametys.runtime.util.I18nizableText;
 import org.ametys.runtime.util.cocoon.ActionResultGenerator;
 import org.ametys.web.cache.PageHelper;
@@ -49,9 +46,6 @@
  */
 public class SubscribeAction extends ServiceableAction
 {  
-    /** The pattern to finally check international phone number */
-    protected static final Pattern PHONE_NUMBER_INTERNATIONAL_VALIDATOR = Pattern.compile("^\\+[0-9]{6,}$");
-    
     /** Ametys object resolver. */
     protected AmetysObjectResolver _resolver;
     
@@ -135,13 +129,13 @@
         else
         {
             phoneNumber = phoneNumber.trim();           
-            phoneNumber = _transformPhoneNumber(phoneNumber);
+            phoneNumber = SMSHelper.transformPhoneNumber(phoneNumber);
 
-            if (!_checkPhoneNumber(phoneNumber))
+            if (!SMSHelper.checkPhoneNumber(phoneNumber))
             {   
                 errors.add(new I18nizableText("plugin.sms", "PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_PHONE"));
             }
-            else if (!PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
+            else if (!SMSHelper.PHONE_NUMBER_INTERNATIONAL_VALIDATOR.matcher(phoneNumber).matches())
             {
                 errors.add(new I18nizableText("plugin.sms", "PLUGINS_SMS_SUBSCRIPTION_SERVICE_FORM_ERROR_PHONE2"));                
                 getLogger().error("The phone number entered by the user: " + request.getParameter("phone") + ", we tried to insert this number: " + phoneNumber + ". This is not match the internationnal format. Look the global configuration.");
@@ -188,74 +182,4 @@
             }
         }
     }
-
-    // Transform the phoneNumber to the international format defined by the admin
-    private String _transformPhoneNumber(String phoneNumber)
-    {
-        String regexConvert = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.convert");
-        String[] tabRegexConvert = StringUtils.split(regexConvert, "\n");
-        String newPhoneNumber = phoneNumber;
-
-        for (int i = 0; i < tabRegexConvert.length; i++)
-        {
-            String[] regexParams = StringUtils.split(tabRegexConvert[i], "/");
-            String regex = regexParams[0];
-            String replaceExpr = regexParams[1];
-            
-            // ensure regexp takes the whole expression
-            if (!regex.startsWith("^"))
-            {
-                regex = "^" + regex;
-            }
-            if (!regex.endsWith("$"))
-            {
-                regex = regex + "$";
-            }
-             
-            // We transform the phone number to an international phone number
-            Matcher m = Pattern.compile(regex).matcher(phoneNumber);
-            if (m.matches())
-            {
-                newPhoneNumber = replaceExpr;
-                
-                for (int c = 1; c <= m.groupCount(); c++)
-                {
-                    newPhoneNumber = newPhoneNumber.replaceAll("\\{" + c + "\\}", m.group(c));
-                }
-                break;
-            }
-        }
-        
-        return newPhoneNumber;
-    }
-    
-    // Check if the phoneNumber is in the international format defined by the admin
-    private boolean _checkPhoneNumber(String phoneNumber)
-    {
-        String regexPhone = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.autorized");         
-        String[] tabRegexPhone = StringUtils.split(regexPhone, "\n");
-
-        for (String regex : tabRegexPhone)
-        {
-            // ensure regexp takes the whole expression
-            String fullRegex = regex;
-            if (!regex.startsWith("^"))
-            {
-                fullRegex = "^" + fullRegex;
-            }
-            if (!regex.endsWith("$"))
-            {
-                fullRegex = fullRegex + "$";
-            }
-            
-            // We check if the phoneNumber format is correct
-            if (Pattern.compile(fullRegex).matcher(phoneNumber).matches())
-            {
-                return true;
-            }
-        }
-        
-        return false;
-    }
-    
 }
Index: main/plugin-sms/pages/import/import-result-json.xsl
===================================================================
--- main/plugin-sms/pages/import/import-result-json.xsl	(revision 0)
+++ main/plugin-sms/pages/import/import-result-json.xsl	(revision 0)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright 2011 Anyware Services
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+   -->
+<xsl:stylesheet version="1.0" 
+                xmlns:i18n="http://apache.org/cocoon/i18n/2.1"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    
+    <xsl:template match="/ActionResult">
+        <json>
+        <xsl:choose>
+            <xsl:when test="success = 'true'">
+{
+    success: true,
+    subscribedCount: <xsl:value-of select="subscribedCount"/>,
+    existingCount: <xsl:value-of select="existingCount"/>,
+    errorCount: <xsl:value-of select="errorCount"/>,
+    smsListId: "<xsl:value-of select="smsListId"/>"
+}
+            </xsl:when>
+            <xsl:otherwise>
+{
+    success: false,
+    error: "<xsl:value-of select="error"/>"
+}
+            </xsl:otherwise>
+        </xsl:choose>
+        </json>
+    </xsl:template>
+    
+</xsl:stylesheet>
Index: main/plugin-sms/cms-ribbon.xml
===================================================================
--- main/plugin-sms/cms-ribbon.xml	(revision 30961)
+++ main/plugin-sms/cms-ribbon.xml	(working copy)
@@ -45,9 +45,12 @@
 					<large/>
 					<medium>
 						<control id="org.ametys.sms.PhoneNumbers"/>
+						<control id="org.ametys.sms.ImportSubscribersButton"/>
+						<control id="org.ametys.sms.AddNumberButton"/>
+					    <control id="org.ametys.sms.DeleteNumbersButton"/>
 					</medium>
 					<small/>
-				</group> 
+				</group>
 			</groups>
 		</tab>
 	</tabs>
Index: main/plugin-sms/resources/js/org/ametys/sms/subscribers/SubscribersActions.i18n.js
===================================================================
--- main/plugin-sms/resources/js/org/ametys/sms/subscribers/SubscribersActions.i18n.js	(revision 0)
+++ main/plugin-sms/resources/js/org/ametys/sms/subscribers/SubscribersActions.i18n.js	(revision 0)
@@ -0,0 +1,475 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.sms.subscribers.SubscribersActions.importCsv');
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._pluginName;
+org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog;
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv = function(id, config, pluginName)
+{
+	var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
+    var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) { return target.getType() == 'sms-list' });
+    
+    if (target == null)
+    {
+        return;
+    }
+    org.ametys.sms.subscribers.SubscribersActions.importCsv._pluginName = pluginName;
+    
+    var smsListId = target.getParameters().id;
+    if (org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog == null)
+    {
+        org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog = org.ametys.sms.subscribers.SubscribersActions.importCsv._createDialogBox();
+    }
+    
+    org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.show();
+    org.ametys.sms.subscribers.SubscribersActions.importCsv._initForm (smsListId);
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._createDialogBox = function()
+{
+    var pluginName = org.ametys.sms.subscribers.SubscribersActions.importCsv._pluginName;
+    
+    var formPanel = new Ext.form.FormPanel( {
+        labelWidth: 60,
+        bodyStyle: 'padding:5px',
+        fileUpload: true,
+        
+        defaultType: 'textfield',
+        
+        autoScroll: true,
+        items: [
+            new org.ametys.HtmlContainer({
+                id: 'importCsvHint',
+                html: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_HINT'/>",
+                cls: 'dialog-text-hint'
+            }),
+            new Ext.form.Hidden({
+                id: 'smsListId',
+                name: 'smsListId',
+                value: ''
+            }),
+            new org.ametys.form.FileUploadField({
+                id: 'importFile',
+                name: 'importFile',
+                fieldLabel: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_LABEL'/>",
+                desc: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_DESC'/>",
+                width: 300,
+                allowBlank: false,
+                emptyText: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_EMPTY_TEXT'/>",
+                buttonText: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_FILE_BUTTON'/>"
+            }),
+            new org.ametys.form.BooleanField({
+                 name: 'cleanSubscribers',
+                 inputValue: 'true',
+                 hideLabel: true,
+                 boxLabel: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS'/>",
+                 desc: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_CLEAN_SUBSCRIBERS_DESC'/>",
+                 checked: false,
+                 width: 300,
+                 height: 20,
+                 style: {
+                     marginLeft: '30px'
+                 }
+            })
+        ]
+    });
+    
+    var dialog = new org.ametys.DialogBox({
+        layout: 'fit',
+        
+        title: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_TITLE'/>",
+        icon: getPluginResourcesUrl(pluginName) + '/img/import_csv_16.png',
+        
+        width: 450,
+        height: 200,
+        autoScroll: false,
+        
+        items: [formPanel],
+        
+        defaultButton: formPanel.getForm().findField('importFile'),
+        closeAction: 'hide',
+        buttons: [{
+            text: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_OK'/>",
+            handler: org.ametys.sms.subscribers.SubscribersActions.importCsv._ok
+        }, {
+            text: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUBSCRIBERS_DIALOG_BUTTON_CANCEL'/>",
+            handler: org.ametys.sms.subscribers.SubscribersActions.importCsv._cancel
+        }]        
+    });
+    
+    return dialog;
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._initForm = function (smsListId)
+{
+	var form = org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.items.itemAt(0).getForm();
+    form.findField('smsListId').setValue(smsListId);
+    form.findField('importFile').reset();
+    form.findField('cleanSubscribers').setValue(false);
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._ok = function(button, event)
+{
+    var form = org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.items.itemAt(0).getForm();
+    var clean = form.findField('cleanSubscribers').checked;
+    
+    if (clean)
+    {
+        Ext.Msg.confirm(
+            "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_DIALOG_WAIT_TITLE'/>", 
+            "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_CLEAN_CONFIRM'/>", 
+            function (answer) {
+                if (answer == 'yes')
+                {
+                    org.ametys.sms.subscribers.SubscribersActions.importCsv._doImport();
+                }
+            },
+            this
+        );
+    }
+    else
+    {
+        org.ametys.sms.subscribers.SubscribersActions.importCsv._doImport();
+    }
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._doImport = function()
+{
+    var form = org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.items.itemAt(0).getForm();
+    
+    if (form.isValid())
+    {
+        form.submit({
+            url: getPluginDirectUrl(org.ametys.sms.subscribers.SubscribersActions.importCsv._pluginName) + '/subscribers/import',
+            params: {siteName: context.parameters.siteName},
+            
+            waitTitle: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_DIALOG_WAIT_TITLE'/>",
+            waitMsg: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_DIALOG_WAIT_MSG'/>",
+            
+            success: org.ametys.sms.subscribers.SubscribersActions.importCsv._success,
+            failure: org.ametys.sms.subscribers.SubscribersActions.importCsv._failure,
+            
+            errorReader: new Ext.data.XmlReader({
+                record: 'field',
+                success: '@success'
+            })
+        });
+    }
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._cancel = function(button, event)
+{
+    org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.hide();
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._success = function(form, action)
+{
+    var message = "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_START'/>";
+    
+    if (action.result.subscribedCount > 0)
+	{
+    	message = message + action.result.subscribedCount + " ";
+    	message += action.result.subscribedCount == 1 ? "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_SINGLE'/>" : "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_SUBSCRIBED_MULTIPLE'/>";
+	}
+    
+    if (action.result.existingCount > 0)
+    {
+        message = message + action.result.existingCount + " ";
+        message += action.result.existingCount == 1 ? "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_SINGLE'/>" : "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_EXISTING_MULTIPLE'/>";
+    }
+    
+    if (action.result.errorCount > 0)
+    {
+        message = message + action.result.errorCount + " ";
+        message += action.result.errorCount == 1 ? "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_ERROR_SINGLE'/>" : "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_ERROR_MULTIPLE'/>";
+    }
+    
+    Ext.Msg.show({
+        title: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_SUCCESS_TITLE'/>",
+        msg: message,
+        buttons: Ext.Msg.OK,
+        icon: Ext.MessageBox.INFO
+    });
+    
+    org.ametys.sms.subscribers.SubscribersActions.importCsv._dialog.hide();
+    
+    var target = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("sms-list", {'id': action.result.smsListId});
+	var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_METADATACHANGED, null, [target]);
+	org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+	
+	var phoneNumbersTool = org.ametys.uitool.ToolsManager.getInstance().getTool("uitool-sms-phone-numbers");
+	if (phoneNumbersTool != null)
+	{
+		phoneNumbersTool.refresh();
+	}
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._failure = function(form, action)
+{
+    var message = '';
+    var error = action.result.error;
+    
+    if (error == 'unknown-list')
+    {
+        message = "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_ERROR_UNKNOWN_CATEGORY'/>";
+    }
+    else if (error == 'invalid-extension')
+    {
+        message = "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_ERROR_INVALID_FILE'/>";
+    }
+    else if (error == 'rejected-file')
+    {
+        message = "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_ERROR_REJECTED_FILE'/>";
+    }
+    else
+    {
+    	message = "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_ERROR'/>";
+    }
+    
+    Ext.Msg.show({
+        title: "<i18n:text i18n:key='PLUGINS_SMS_IMPORT_ERROR_TITLE'/>",
+        msg: message,
+        buttons: Ext.Msg.OK,
+        icon: Ext.MessageBox.ERROR
+    });
+}
+
+// --------------------------------------------------------------------------------------------------------------------------------------------------
+
+Ext.namespace('org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers');
+
+org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers._pluginName;
+
+org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers = function(id, config, pluginName)
+{
+    var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets(),
+    	targetSmsList = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) {return target.getType() == 'sms-list'});
+    
+    if (targetSmsList == null)
+    {
+        return;
+    }
+    
+    org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers._pluginName = pluginName;
+    
+    var targetPhoneNumbers = org.ametys.messagebus.message.MessageTargetHelper.findAll(targetSmsList.getSubTargets(), function(target) {return target.getType() == 'sms-list-phone-number'});
+    if (targetPhoneNumbers == null)
+    {
+        return;
+    }
+    
+    var smsListId = targetSmsList.getParameters().id;
+        
+    var phoneNumbers = [];
+    Ext.each(targetPhoneNumbers, function(entry) {
+    	phoneNumbers.push(entry.getParameters()["phone-number"]);
+    }, this);
+        
+    Ext.Msg.confirm(
+        "<i18n:text i18n:key='PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TITLE'/>", 
+        "<i18n:text i18n:key='PLUGINS_SMS_DELETE_PHONE_NUMBERS_DIALOG_TEXT'/>", 
+        function (answer) {
+            if (answer == 'yes')
+            {
+                org.ametys.sms.subscribers.SubscribersActions.importCsv._doDelete(smsListId, phoneNumbers);
+            }
+        },
+        this
+    );
+}
+
+org.ametys.sms.subscribers.SubscribersActions.importCsv._doDelete = function(smsListId, phoneNumbers)
+{
+	var params = {};
+	
+	params.smsListId = smsListId;
+	params.phoneNumbers = phoneNumbers;
+	var serverMessage = new org.ametys.servercomm.ServerMessage(org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers._pluginName, '/subscribers/delete-phone-numbers', params, org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers._callback, this, {"smsListId" : smsListId});
+	org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+}
+
+org.ametys.sms.subscribers.SubscribersActions.deletePhoneNumbers._callback = function(response, args)
+{
+	if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key="PLUGINS_SMS_DELETE_PHONE_NUMBERS_ERROR"/>", response, "org.ametys.sms.DeleteNumbersButton"))
+	{
+		return;
+	}
+	
+	var target = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("sms-list", {'id': args.smsListId});
+	var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_METADATACHANGED, null, [target]);
+	org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+	
+	var phoneNumbersTool = org.ametys.uitool.ToolsManager.getInstance().getTool("uitool-sms-phone-numbers");
+	if (phoneNumbersTool != null)
+	{
+		phoneNumbersTool.refresh();
+	}
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------------------
+
+Ext.namespace('org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber');
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._pluginName;
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox;
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._formPanel;
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber = function(id, config, pluginName)
+{
+	 var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets(),
+	 	targetSmsList = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) {return target.getType() == 'sms-list'});
+	 
+	 if (targetSmsList == null)
+	 {
+	     return;
+	 }
+	
+	var smsListId = targetSmsList.getParameters().id;
+	 
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._pluginName = pluginName;
+	
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._initializeBox();
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox.show();
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._initForm (smsListId);
+}
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._initializeBox = function()
+{
+	if (!org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox)
+	{		
+		org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._formPanel = new Ext.form.FormPanel({
+	        labelWidth: 120,
+	        defaultType: 'textfield',
+	        labelAlign: 'right',
+	        
+	        items: [
+	            new org.ametys.HtmlContainer({
+	                html: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_HINT'/>",
+	                cls: 'dialog-text-hint'
+	            }),
+	            new Ext.form.Hidden({
+	                id: 'smsListId',
+	                name: 'smsListId',
+	                value: ''
+	            }),
+	            new org.ametys.form.TextField({
+	            	id: 'phone-number',
+	    	        name: 'phone-number',
+	    	        fieldLabel: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_NUMBER_FIELD_LABEL'/>",
+	    	        desc: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_NUMBER_FIELD_DESC'/>",
+	    	        width: 100,
+	    		    allowBlank: false
+	    	    })
+	        ],
+	        
+	        bodyStyle: 'padding: 5px',
+	        autoScroll: true
+	    });
+		
+		org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox = new org.ametys.DialogBox({
+	        layout: 'fit',
+	        title: '<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_TITLE'/>',
+	        width: 435,
+	        height: 150,
+			icon : getPluginResourcesUrl(org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._pluginName) + "/img/import_csv_16.png",
+	        autoScroll: false,	        
+	        items: [org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._formPanel],        
+	        closeAction: 'hide',
+	        buttons: [{
+	            text: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_BUTTON_OK'/>",
+	            handler: org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._doAdd
+	        }, {
+	            text: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_DIALOG_BUTTON_CANCEL'/>",
+	            handler: org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._cancel
+	        }]        
+	    });
+	}
+}
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._initForm = function (smsListId)
+{
+	var form = org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox.items.itemAt(0).getForm();
+    form.findField('smsListId').setValue(smsListId);
+    form.findField('phone-number').setValue('');
+}
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._doAdd = function()
+{
+	var form = org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox.items.itemAt(0).getForm();
+	
+	if (form.isValid())
+	{
+		var params = {};
+		
+		params.smsListId = form.findField('smsListId').getValue();
+		params.phoneNumber = form.findField('phone-number').getValue();
+		
+		var serverMessage = new org.ametys.servercomm.ServerMessage(org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._pluginName, '/subscribers/add-phone-number', params, org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._addCb, this, {"smsListId" : params.smsListId});
+		org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+	}
+}
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._cancel = function()
+{
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox.hide();
+}
+
+org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._addCb = function(response, args)
+{
+	if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key="PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR"/>", response, "org.ametys.sms.AddNumberButton"))
+	{
+		return;
+	}
+	
+	org.ametys.sms.subscribers.SubscribersActions.addPhoneNumber._dialogBox.hide();
+	
+	var error = response.selectSingleNode("ActionResult/error")[org.ametys.servercomm.ServerComm.xmlTextContent];
+	var message;
+	if (error == "already-exists")
+	{
+		message = "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_ALREADY_EXISTS'/>";
+	}
+	
+	if (error == "wrong-format")
+	{
+		message = "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR_WRONG_FORMAT'/>";
+	}
+	
+	if (error != '' &amp;&amp; error != null)
+	{
+		Ext.Msg.show({
+	        title: "<i18n:text i18n:key='PLUGINS_SMS_ADD_PHONE_NUMBER_ERROR'/>",
+	        msg: message,
+	        buttons: Ext.Msg.OK,
+	        icon: Ext.MessageBox.ERROR
+	    });
+	}
+	else
+	{
+		var target = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("sms-list", {'id': args.smsListId});
+		var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_METADATACHANGED, null, [target]);
+		org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+		
+		var phoneNumbersTool = org.ametys.uitool.ToolsManager.getInstance().getTool("uitool-sms-phone-numbers");
+		if (phoneNumbersTool != null)
+		{
+			phoneNumbersTool.refresh();
+		}
+	}
+}
+
Index: main/plugin-sms/resources/js/org/ametys/sms/tool/PhoneNumbersTool.i18n.js
===================================================================
--- main/plugin-sms/resources/js/org/ametys/sms/tool/PhoneNumbersTool.i18n.js	(revision 30961)
+++ main/plugin-sms/resources/js/org/ametys/sms/tool/PhoneNumbersTool.i18n.js	(working copy)
@@ -207,7 +207,7 @@
 		var subtargets = target.getSubTargets();
 		for (var i=0; i &lt; records.length; i++)
 		{
-			subtargets.push(org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("sms-list-phone-number", {'phoneNumber': records[i].data.number}));
+			subtargets.push(org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("sms-list-phone-number", {'phone-number': records[i].data["phone-number"]}));
 		}
 		
 		var message = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, target != null ? [target] : []);
