Index: main/plugin-web/i18n/messages_en.xml
===================================================================
--- main/plugin-web/i18n/messages_en.xml	(revision 32549)
+++ main/plugin-web/i18n/messages_en.xml	(working copy)
@@ -1118,6 +1119,8 @@
 	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR">An error has occurred. Unable to schedule the publication.</message>
 	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_INTERVAL_ERROR">The end date must be greater than the start date</message>
 	
+	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_REFRESH_ERROR">An error has occurred while refreshing the publication button.</message>
+	
 	<!-- +
 	     | PREVIEW 
 	     + -->
Index: main/plugin-web/i18n/messages_fr.xml
===================================================================
--- main/plugin-web/i18n/messages_fr.xml	(revision 32549)
+++ main/plugin-web/i18n/messages_fr.xml	(working copy)
@@ -1119,6 +1120,8 @@
 	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR">Une erreur est survenue. Impossible de planifier la publication.</message>
 	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_INTERVAL_ERROR">La date de fin doit être supérieure à la date de début</message>
 	
+	<message key="PLUGINS_WEB_PUBLICATION_SCHEDULE_REFRESH_ERROR">Une erreur est survenue lors du raffraîchissement du bouton de publication.</message>
+	
 	<!-- +
 	     | PREVIEW 
 	     + -->

Index: main/plugin-web/plugin.xml
===================================================================
--- main/plugin-web/plugin.xml	(revision 32549)
+++ main/plugin-web/plugin.xml	(working copy)
@@ -4005,66 +4019,72 @@
     		</extension> -->
     	</extensions>
     </feature>
-    
+
     <feature name="scheduled-publication">
     	<extensions>
-    		<!-- <extension id="org.ametys.web.publication.schedule"
+    		<extension id="org.ametys.web.publication.schedule"
 					   point="org.ametys.core.ui.RibbonControlsManager" 
 					   class="org.ametys.web.clientsideelement.ScheduledPageClientSideElement">
-				<action class="org.ametys.ribbon.control.button.ScheduleActionButton">
-					<param name="action">org.ametys.web.page.SchedulePublication</param>
+				<class name="Ametys.plugins.web.page.controller.ScheduledPublicationController">
+					<action>Ametys.plugins.web.page.SchedulePublication.schedulePublication</action>
+					<selection-target-type>^page$</selection-target-type>
+                    <selection-target-parameter>
+                        <name>isModifiable</name>
+                        <value>true</value>
+                    </selection-target-parameter>
 					
-					<param name="label" i18n="true">PAGE_SCHEDULE_PUBLICATION_LABEL</param>
-					<param name="default-description" i18n="true">PAGE_SCHEDULE_PUBLICATION_DESCRIPTION</param>
-					<param name="refresh-description" i18n="true">PAGE_SCHEDULE_PUBLICATION_REFRESHING</param>
-					<param name="multiselection-disabled-description" i18n="true">PAGE_SCHEDULE_PUBLICATION_TOOMANYPAGE</param>
-					<param name="no-modifiable-description" i18n="true">PLUGINS_WEB_UITOOL_PAGE_NOMODIFIABLE_DESC</param>
-					<param name="error-description" i18n="true">PAGE_SCHEDULE_PUBLICATION_ERROR</param>
-					<param name="norights-description" i18n="true">PAGE_SCHEDULE_PUBLICATION_NORIGHTS</param>
+					<toggle-enabled>true</toggle-enabled>
 					
-					<param name="icon-small" file="true">img/publication/publication_16.png</param>
-					<param name="icon-medium" file="true">img/publication/publication_32.png</param>
-					<param name="icon-large" file="true">img/publication/publication_48.png</param>
+					<label i18n="true">PAGE_SCHEDULE_PUBLICATION_LABEL</label>
+					<default-description i18n="true">PAGE_SCHEDULE_PUBLICATION_DESCRIPTION</default-description>
+					<refresh-description i18n="true">PAGE_SCHEDULE_PUBLICATION_REFRESHING</refresh-description>
+					<multiselection-disabled-description i18n="true">PAGE_SCHEDULE_PUBLICATION_TOOMANYPAGE</multiselection-disabled-description>
+					<nomodifiable-page-description i18n="true">PLUGINS_WEB_UITOOL_PAGE_NOMODIFIABLE_DESC</nomodifiable-page-description>
+					<error-description i18n="true">PAGE_SCHEDULE_PUBLICATION_ERROR</error-description >
+					<noright-page-description i18n="true">PAGE_SCHEDULE_PUBLICATION_NORIGHTS</noright-page-description>
 					
-					<param name="on-icon-small" file="true">img/publication/publication_on_16.png</param>
-					<param name="on-icon-medium" file="true">img/publication/publication_on_32.png</param>
-					<param name="on-icon-large" file="true">img/publication/publication_on_48.png</param>
+					<icon-small file="true">img/publication/publication_16.png</icon-small>
+					<icon-medium file="true">img/publication/publication_32.png</icon-medium>
+					<icon-large file="true">img/publication/publication_48.png</icon-large>
 					
-					<param name="forthcoming-icon-small" file="true">img/publication/publication_fc_16.png</param>
-					<param name="forthcoming-icon-medium" file="true">img/publication/publication_fc_32.png</param>
-					<param name="forthcoming-icon-large" file="true">img/publication/publication_fc_48.png</param>
+					<on-icon-small file="true">img/publication/publication_on_16.png</on-icon-small>
+					<on-icon-medium file="true">img/publication/publication_on_32.png</on-icon-medium>
+					<on-icon-large file="true">img/publication/publication_on_48.png</on-icon-large>
 					
-					<param name="outofdate-icon-small" file="true">img/publication/publication_ood_16.png</param>
-					<param name="outofdate-icon-medium" file="true">img/publication/publication_ood_32.png</param>
-					<param name="outofdate-icon-large" file="true">img/publication/publication_ood_48.png</param>
+					<forthcoming-icon-small file="true">img/publication/publication_fc_16.png</forthcoming-icon-small>
+					<forthcoming-icon-medium file="true">img/publication/publication_fc_32.png</forthcoming-icon-medium>
+					<forthcoming-icon-large file="true">img/publication/publication_fc_48.png</forthcoming-icon-large>
 					
-					<param name="scheduled-page-valid-start-description" i18n="true">PAGE_SCHEDULED_DESCRIPTION_START</param>
-					<param name="scheduled-page-valid-description" i18n="true">PAGE_SCHEDULED_DESCRIPTION</param>
-					<param name="scheduled-page-valid-end-description" i18n="true">PAGE_SCHEDULED_DESCRIPTION_END</param>
+					<outofdate-icon-small file="true">img/publication/publication_ood_16.png</outofdate-icon-small>
+					<outofdate-icon-medium file="true">img/publication/publication_ood_32.png</outofdate-icon-medium>
+					<outofdate-icon-large file="true">img/publication/publication_ood_48.png</outofdate-icon-large>
 					
-					<param name="scheduled-page-outofdate-start-description" i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION_START</param>
-					<param name="scheduled-page-outofdate-description" i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION</param>
-					<param name="scheduled-page-outofdate-end-description" i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION_END</param>
+					<scheduled-page-valid-start-description i18n="true">PAGE_SCHEDULED_DESCRIPTION_START</scheduled-page-valid-start-description>
+					<scheduled-page-valid-description i18n="true">PAGE_SCHEDULED_DESCRIPTION</scheduled-page-valid-description>
+					<scheduled-page-valid-end-description i18n="true">PAGE_SCHEDULED_DESCRIPTION_END</scheduled-page-valid-end-description>
 					
-					<param name="scheduled-page-forthcoming-start-description" i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION_START</param>
-					<param name="scheduled-page-forthcoming-description" i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION</param>
-					<param name="scheduled-page-forthcoming-end-description" i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION_END</param>
+					<scheduled-page-outofdate-start-description i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION_START</scheduled-page-outofdate-start-description>
+					<scheduled-page-outofdate-description i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION</scheduled-page-outofdate-description>
+					<scheduled-page-outofdate-end-description i18n="true">PAGE_SCHEDULED_OUTOFDATE_DESCRIPTION_END</scheduled-page-outofdate-end-description>
 					
-					<param name="scheduled-page-startdate-start-description" i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION_START</param>
-					<param name="scheduled-page-startdate-description" i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION</param>
-					<param name="scheduled-page-startdate-end-description" i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION_END</param>
+					<scheduled-page-forthcoming-start-description i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION_START</scheduled-page-forthcoming-start-description>
+					<scheduled-page-forthcoming-description i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION</scheduled-page-forthcoming-description>
+					<scheduled-page-forthcoming-end-description i18n="true">PAGE_SCHEDULED_FORTHCOMING_DESCRIPTION_END</scheduled-page-forthcoming-end-description>
 					
-					<param name="scheduled-page-enddate-start-description" i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION_START</param>
-					<param name="scheduled-page-enddate-description" i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION</param>
-					<param name="scheduled-page-enddate-end-description" i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION_END</param>
-				</action>
+					<scheduled-page-startdate-start-description i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION_START</scheduled-page-startdate-start-description>
+					<scheduled-page-startdate-description i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION</scheduled-page-startdate-description>
+					<scheduled-page-startdate-end-description i18n="true">PAGE_SCHEDULED_STARTDATE_DESCRIPTION_END</scheduled-page-startdate-end-description>
+					
+					<scheduled-page-enddate-start-description i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION_START</scheduled-page-enddate-start-description>
+					<scheduled-page-enddate-description i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION</scheduled-page-enddate-description>
+					<scheduled-page-enddate-end-description i18n="true">PAGE_SCHEDULED_ENDDATE_DESCRIPTION_END</scheduled-page-enddate-end-description>
+				</class>
 				<scripts>
-					<file>js/org/ametys/ribbon/control/button/ScheduleActionButton.js</file>
-					<file>js/org/ametys/web/page/SchedulePublication.js</file>
+					<file>js/Ametys/plugins/web/page/controller/ScheduledPublicationController.js</file>
+					<file>js/Ametys/plugins/web/page/SchedulePublication.js</file>
 				</scripts>
 				<right>Web_Rights_Page_Schedule</right>
-			</extension> -->
-    	
+			</extension>
     	</extensions>
     </feature>
 		
Index: main/plugin-web/resources/js/Ametys/plugins/web/page/SchedulePublication.js
===================================================================
--- main/plugin-web/resources/js/Ametys/plugins/web/page/SchedulePublication.js	(revision 0)
+++ main/plugin-web/resources/js/Ametys/plugins/web/page/SchedulePublication.js	(revision 0)
@@ -0,0 +1,273 @@
+/*
+ *  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.
+ */
+
+/**
+ * Schedule the publication of page
+ */
+Ext.define(
+	'Ametys.plugins.web.page.SchedulePublication', 
+	{
+		singleton: true,
+		
+		/**
+		 * @private
+		 * @property {Ametys.window.DialogBox} _box The dialog box
+		 */
+	
+		/**
+		 * @private
+		 * @property {Ext.form.Basic} _form the form
+		 */
+		
+		/**
+		 * @private
+		 * @property {Boolean} _initialized Is the dialog box initialized ?
+		 */
+		
+		/**
+		 * Schedule the publication of a page
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller the controller calling this function
+		 */
+		schedulePublication: function(controller)
+		{
+			var targets = controller.getMatchingTargets();
+			if (targets.length > 0)
+			{
+				this._delayedInitialize(controller);
+				
+				this._initForm(controller, targets[0].getParameters().id);
+				this._box.show();
+			}
+		},
+	
+		/**
+		 * Initialize the form with values provided by the server
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller the button controller
+		 */
+		_delayedInitialize: function (controller)
+		{
+			if (this._initialized)
+			{
+				return;
+			}
+			
+			var formPanel = Ext.create('Ext.form.Panel', {
+				labelWidth: 120,
+				bodyStyle: 'padding:10px',
+				border: false,
+				
+				defaultType :'datefield',
+				
+				defaults: {
+					cls: 'ametys',
+					labelWidth: 100,
+					labelAlign: 'right',
+					labelSeparator: '',
+					msgTarget: 'side'
+				},
+				
+				autoScroll: true,
+				
+				items: [ 
+						{
+							name: 'page.input.date.single.publicationStartDate',
+				        	fieldLabel: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_START_DATE'/>",
+					        ametysDescription: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_START_DATE_DESC'/>"
+				        },
+				        {
+				        	name: 'page.input.date.single.publicationEndDate',
+				        	fieldLabel: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_END_DATE'/>",
+					        ametysDescription: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_END_DATE_DESC'/>"
+				        }
+				]
+			});
+	
+			this._form = formPanel.getForm();
+		
+			this._box = Ext.create('Ametys.window.DialogBox',  {
+				
+				title : "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_TITLE'/>",
+				icon : Ametys.getPluginResourcesPrefix('web') + "/img/publication/publication_16.png",
+				
+				width: 400,
+				height: 220,
+				
+				layout: 'form',
+	
+				items : [{
+							cls: 'text',
+							html: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_HINT'/>",
+							border: false
+						  },
+						  formPanel 
+						 ],
+				
+				closeAction: 'hide',
+				buttons : [{
+					text: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_OK_BTN'/>",
+					handler: Ext.bind(this._ok, this, [controller], false)
+				}, {
+					text: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_CANCEL_BTN'/>",
+					handler: Ext.bind(this._cancel, this)
+				} ]
+			});
+			
+			this._initialized = true;
+		},
+	
+		/**
+		 * Initialize the form with values provided by the server
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller the button controller
+		 * @param {String} pageId the id of the page
+		 */
+		_initForm: function (controller, pageId)
+		{
+			controller.serverCall('getPublicationDates',
+					[pageId], 
+					Ext.bind(this._initFormCb, this),
+					{ 
+						errorMessage: { 
+							msg: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR'/>", 
+							category: this.self.getName() 
+						}
+					},
+					true
+			);
+		},
+		
+		/**
+		 * @private
+		 * Callback for the form initialization process
+		 * @param {Object} response the server's response
+		 * @param {String} response.id the page's id
+		 * @param {String} [response.page['start-date']] the publication starting date
+		 * @param {String} [response.page['end-date']] the publication ending date
+		 * @param {Object} args the callback arguments
+		 */
+		_initFormCb: function(response, args)
+		{
+			var startDate = response.page['start-date'];
+			var endDate = response.page['end-date'];
+	
+			this._form.findField('page.input.date.single.publicationStartDate').setValue(!Ext.isEmpty(startDate) ? Ext.Date.parse(startDate, Ext.Date.patterns.ISO8601DateTime) : '');
+			this._form.findField('page.input.date.single.publicationEndDate').setValue(!Ext.isEmpty(endDate) ? Ext.Date.parse(endDate, Ext.Date.patterns.ISO8601DateTime) : '');
+		},
+	
+		/**
+		 * @private
+		 * Saving process
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller the button controller
+		 */
+		_ok: function(controller)
+		{
+			var params = this._getParams(controller);
+			if (params == null)
+			{
+				return;
+			}
+			
+			Ametys.data.ServerComm.callMethod({
+				role: "org.ametys.web.repository.page.PageDAO",
+				methodName: 'setMetadata',
+				parameters:  [params.id, params.values],
+				callback: {
+					handler: this._okCb,
+					scope: this,
+					arguments: {
+						pageId: params.id
+					}
+				},
+				errorMessage: {
+					category: this.self.getName(),
+					msg: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR'/>"
+				}
+			});
+		},
+	
+		/**
+		 * @private
+		 * Get the parameters of the server call
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller the button controller
+		 */
+		_getParams: function(controller)
+		{
+			var startDateField = this._form.findField('page.input.date.single.publicationStartDate');
+			var endDateField = this._form.findField('page.input.date.single.publicationEndDate');
+			var pageTargets = controller.getMatchingTargets();
+			
+			if (!startDateField.isValid() || !endDateField.isValid() || !(pageTargets.length > 0))
+			{
+				return null;
+			}
+			
+			var params = {},
+				values = {};
+			
+			var startDate = startDateField.getValue();
+			var endDate = endDateField.getValue();
+			
+			// Check date consistency
+			if (!Ext.isEmpty(endDate) && !Ext.isEmpty(startDate) && endDate.getTime() - startDate.getTime() <= 0)
+			{
+				endDateField.markInvalid ("<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_INTERVAL_ERROR'/>");
+				return;
+			}
+			
+			params.id = pageTargets[0].getParameters().id;
+			
+			// Server expects a Map<String, Array>
+			values[startDateField.getName()] = [!Ext.isEmpty(startDate) ? Ext.Date.format(startDate, Ext.Date.patterns.ISO8601DateTime) : ''];
+			values[endDateField.getName()] = [!Ext.isEmpty(endDate) ? Ext.Date.format(endDate, Ext.Date.patterns.ISO8601DateTime) : ''];
+			
+			params.values = values;
+			
+			return params;
+		},
+		
+		/**
+		 * @private
+		 * @param {Object} response the server's response
+		 * @param {Object} args the callback arguments
+		 */
+		_okCb: function (response, args)
+		{
+			this._box.hide();
+			
+			var target = Ext.create('Ametys.message.MessageTarget', {
+							type: Ametys.message.MessageTarget.PAGE,
+							parameters: {id: args.pageId}
+						});
+
+			
+			Ext.create('Ametys.message.Message', {
+				type: Ametys.message.Message.MODIFIED,
+				targets: [target],
+				parameters: {
+					major: false
+				}
+			});
+		},
+		
+		/**
+		 * @private
+		 * Handler invoked when the box's 'Cancel' button is clicked. Hides the box.
+		 */
+		_cancel: function ()
+		{
+			this._box.hide();
+		}
+	}
+);
Index: main/plugin-web/resources/js/Ametys/plugins/web/page/controller/ScheduledPublicationController.js
===================================================================
--- main/plugin-web/resources/js/Ametys/plugins/web/page/controller/ScheduledPublicationController.js	(revision 0)
+++ main/plugin-web/resources/js/Ametys/plugins/web/page/controller/ScheduledPublicationController.js	(revision 0)
@@ -0,0 +1,260 @@
+/*
+ *  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.
+ */
+
+/**
+ * Schedule the publication of page
+ */
+Ext.define(
+	'Ametys.plugins.web.page.controller.ScheduledPublicationController', 
+	{
+		extend: 'Ametys.ribbon.element.ui.ButtonController',
+
+		/**
+		 * @cfg {String} [on-icon-small] The small icon when there is a publication scheduled
+		 */
+		/**
+		 * @cfg {String} [on-icon-medium] The medium icon when there is a publication scheduled
+		 */
+		/**
+		 * @cfg {String} [on-icon-large] The large icon when there is a publication scheduled
+		 */
+		
+		/**
+		 * @cfg {String} [forthcoming-icon-small] The small icon when there is a forthcoming publication
+		 */
+		/**
+		 * @cfg {String} [forthcoming-icon-medium] The medium icon when there is a forthcoming publication
+		 */
+		/**
+		 * @cfg {String} [forthcoming-icon-large] The large icon when there is a forthcoming publication
+		 */
+		
+		/**
+		 * @cfg {String} [outofdate-icon-small] The small icon when there is an outdated publication
+		 */
+		/**
+		 * @cfg {String} [outofdate-icon-medium] The medium icon when there is an outdated publication
+		 */
+		/**
+		 * @cfg {String} [outofdate-icon-large] The large icon when there is an outdated publication
+		 */
+		
+		/**
+		 * @cfg {String} [forthcoming-icon-small] The small icon when there is a forthcoming publication
+		 */
+		/**
+		 * @cfg {String} [forthcoming-icon-medium] The medium icon when there is a forthcoming publication
+		 */
+		/**
+		 * @cfg {String} [forthcoming-icon-large] The large icon when there is a forthcoming publication
+		 */
+		constructor: function(config)
+		{
+			this.callParent(arguments);
+			
+			Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
+		},
+		
+		/**
+		 * @private
+		 * Listener handler for modified messages
+		 * @param {Ametys.message.Message} message the message
+		 */
+		_onModified: function(message)
+		{
+			this.refresh();
+		},
+		
+		updateState: function()
+		{
+			this._getStatus(this.getMatchingTargets());
+		},
+		
+		/**
+		 * @private
+		 * Get the status
+		 * @param targets The page targets
+		 */
+		_getStatus: function (targets)
+		{
+			if (targets.length > 0)
+			{
+				var pageIds = [];
+				Ext.Array.each(targets, function(target) {
+					pageIds.push(target.getParameters().id);
+				});
+
+				this.serverCall('getStatus',
+						[pageIds], 
+						Ext.bind(this._getStatusCb, this),
+						{ 
+							errorMessage: { 
+								msg: "<i18n:text i18n:key='PLUGINS_WEB_PUBLICATION_SCHEDULE_REFRESH_ERROR'/>",
+								category: this.self.getName() 
+							}
+						},
+						true
+				);
+			}
+		},
+		
+		/**
+		 * @private
+		 * Callback for the button's status computation process
+		 * @param {Object} response the server's response
+		 * @param {String} params['scheduled-pages'] the pages having a scheduled publication
+		 * @param {String} params['noright-pages'] the pages for which the user doest not have the right to schedule a publication
+		 * @param {String} params['scheduled-valid-pages'] the pages within publication interval
+		 * @param {String} params['scheduled-outofdate-pages'] the pages outside publication interval
+		 * @param {String} params['scheduled-forthcoming-pages'] the forthcoming pages
+		 * @param {Object} args the callback arguments
+		 */
+		_getStatusCb: function(response, args)
+		{
+			// Update the tooltip
+			this._updateTooltipDescription(this.getInitialConfig('default-description'), response);
+			
+			var isDisabled = response['noright-pages'].length > 0; 
+			if (isDisabled)
+			{
+				tooltip.setAdditionalDescription(this.getInitialConfig()["noright-page-description"]);
+			}
+			
+			// Update icon
+			this._setIcons(response);
+			
+			var nbScheduledPages = parseInt(response['scheduled-page-size']);
+		
+			// Update status
+			this.toggle(nbScheduledPages > 0);
+			this.setDisabled(isDisabled);
+		},
+		
+		
+		/**
+		 * @private
+		 * Update the tooltip description according state of the current selection
+		 * @param description The initial description. Can be empty.
+		 * @param params The JSON result received
+		 */
+		_updateTooltipDescription: function (description, params)
+		{
+			description = this._handlingMultiple(description, "scheduled-page-valid", params['scheduled-valid-pages']);
+			description = this._handlingMultiple(description, "scheduled-page-outofdate", params['scheduled-outofdate-pages']);
+			description = this._handlingMultiple(description, "scheduled-page-forthcoming", params['scheduled-forthcoming-pages']);
+			
+			this.setDescription (description);
+		},
+		
+		/**
+		 * @private
+		 * Add text to description
+		 * @param description The initial description to concatenate. Can be empty.
+		 * @param {String} prefix The parameters prefix to used to retrieve the start and end description. The start and end description are retrieved from initial configuration with [prefix]-start-description and [prefix]-end-description
+		 * @param {Object[]} pages The concerned pages. If empty, no text will be concatenated
+		 */
+		_handlingMultiple: function(description, prefix, pages)
+		{
+			if (pages.length > 0)
+			{
+				if (description != "")
+				{
+					description += "<br/><br/>";
+				}
+				
+				for (var i=0; i < pages.length; i++)
+				{
+					description += this.getInitialConfig(prefix + "-start-description");
+					description += pages[i].description;
+					description += this.getInitialConfig(prefix + "-end-description");
+					
+					if (i != 0) 
+					{
+						description += "<br/>";
+					}
+					
+					if (pages[i]['start-date'] != null)
+					{
+						description += "<br/>";
+						description += this.getInitialConfig()["scheduled-page-startdate-start-description"];
+						description += pages[i]['start-date'];
+						description += this.getInitialConfig()["scheduled-page-startdate-end-description"];
+					}
+					
+					if (pages[i]['end-date'] != null)
+					{
+						description += "<br/>";
+						description += this.getInitialConfig()["scheduled-page-enddate-start-description"];
+						description += pages[i]['end-date'];
+						description += this.getInitialConfig()["scheduled-page-enddate-end-description"];
+					}
+				}
+			}
+			
+			return description;
+		},
+		
+		/**
+		 * @private
+		 * Set the icons according to the button's status
+		 * @param {Object} params the parameters
+		 * @param {String} params['scheduled-pages'] the pages having a scheduled publication
+		 * @param {String} params['noright-pages'] the pages for which the user doest not have the right to schedule a publication
+		 * @param {String} params['scheduled-valid-pages'] the pages within publication interval
+		 * @param {String} params['scheduled-outofdate-pages'] the pages outside publication interval
+		 * @param {String} params['scheduled-forthcoming-pages'] the forthcoming pages
+		 */
+		_setIcons: function(params)
+		{
+			var nbScheduledPages = params['scheduled-pages'].length;
+			var nbValidPages = params['scheduled-valid-pages'].length;
+			var nbOutOfDatePages = params['scheduled-outofdate-pages'].length;
+			var nbForthComingPages = params['scheduled-forthcoming-pages'].length;
+			
+			var iconSmall, iconMedium, iconLarge;
+			
+			if (nbScheduledPages > 0 && nbValidPages > 0)
+			{
+				iconSmall = this.getInitialConfig()['on-icon-small'];
+				iconMedium = this.getInitialConfig()['on-icon-medium'];
+				iconLarge = this.getInitialConfig()['on-icon-large'];
+			}
+			
+			if (nbScheduledPages > 0 && nbForthComingPages > 0)
+			{
+				iconSmall = this.getInitialConfig()['forthcoming-icon-small'];
+				iconMedium = this.getInitialConfig()['forthcoming-icon-medium'];
+				iconLarge = this.getInitialConfig()['forthcoming-icon-large'];
+			}
+			
+			if (nbScheduledPages > 0 && nbOutOfDatePages > 0)
+			{
+				iconSmall = this.getInitialConfig()['outofdate-icon-small'];
+				iconMedium = this.getInitialConfig()['outofdate-icon-medium'];
+				iconLarge = this.getInitialConfig()['outofdate-icon-large'];
+			}
+			
+			if (nbScheduledPages == 0)
+			{
+				iconSmall = this.getInitialConfig()['icon-small'];
+				iconMedium = this.getInitialConfig()['icon-medium'];
+				iconLarge = this.getInitialConfig()['icon-large'];
+			}
+			
+			this.setIcons(iconSmall, iconMedium, iconLarge);
+		}
+	}
+);
Index: main/plugin-web/resources/js/org/ametys/ribbon/control/button/ScheduleActionButton.js
===================================================================
--- main/plugin-web/resources/js/org/ametys/ribbon/control/button/ScheduleActionButton.js	(revision 32509)
+++ main/plugin-web/resources/js/org/ametys/ribbon/control/button/ScheduleActionButton.js	(nonexistent)
@@ -1,301 +0,0 @@
-/*
- *  Copyright 2010 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.ribbon.control.button');
-
-org.ametys.ribbon.control.button.ScheduleActionButton = function(id, configuration, pluginName) 
-{
-	org.ametys.ribbon.control.button.ScheduleActionButton.superclass.constructor.call(this, id, configuration, pluginName);
-};
-
-Ext.extend(org.ametys.ribbon.control.button.ScheduleActionButton, org.ametys.ribbon.control.button.ActionButton, {});
-
-/**
- * @private
- * @property {Map<String, String>} _lastParams The parameters received on the last refresh
- */
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._lastParams;
-
-/**
- * @private
- * @property {String[]} _currentPagesId The pages id currently selected or null. 
- */
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._currentPagesId;
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._areTheSame = function (tab1, tab2)
-{
-	if (tab1 == tab2)
-	{
-		return true;
-	}
-
-	if (tab1 == null || tab2 == null || tab1.length != tab2.length)
-	{
-		return false;
-	}	
-	
-	for (var i=0; i < tab1.length; i++)
-	{
-		if (tab2.indexOf(tab1[i]) == -1)
-		{
-			return false;
-		}
-	}
-	
-	return true;
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._doReload = function()
-{
-	if (this._currentPagesId.length == 0)
-	{
-		this.getElements().each(function(element) { element.setDisabled(true); });
-		this.updateTooltip(this.getConfiguration()["default-description"]);
-	}
-	if (this._currentPagesId.length == 1)
-	{
-		this.getElements().each(function(element) { element.setDisabled(true); element.refreshing(); });
-		this.updateTooltip(this.getConfiguration()["refresh-description"]);
-		org.ametys.ribbon.RibbonManager.getInstance().refreshElement('control', this, {targets: this._currentPagesId});
-	}
-	else
-	{
-		this.getElements().each(function(element) { element.setDisabled(true); });
-		this.updateTooltip(this.getConfiguration()["multiselection-disabled-description"]);
-	}
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype.onMessage = function (messages)
-{
-	// SELECTION
-	for (var i=messages.length - 1; i >= 0; i--)
-	{
-		if (messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED)
-		{
-			var targets = messages[i].getTargets();
-			var allTargets = org.ametys.messagebus.message.MessageTargetHelper.findAll(targets, this.checkTarget());
-			var allTargetsId = this._convertToId(allTargets);
-			
-			// No page selected
-			if (allTargets.length == 0)
-			{
-				this._currentPagesId = [];
-				this._doReload();
-			}
-			// A single target selected 
-			else if (allTargets.length == 1)
-			{
-				if (!this._areTheSame(this._currentPagesId, allTargetsId))
-				{
-					this._currentPagesId = allTargetsId;
-					
-					if (allTargets[0].getParameters().isModifiable != 'true')
-					{
-						var iconSmall = context.contextPath + this.getConfiguration()['icon-small'];
-						var iconLarge = context.contextPath + this.getConfiguration()['icon-medium'];
-						
-						this.getElements().each(
-								function (element) 
-								{
-									element.iconSmall = iconSmall;
-									element.iconLarge = iconLarge;
-								}
-							);
-						
-						this.getElements().each(function(element) { element.updateIcon(); });
-						this.updateTooltip(this.getConfiguration()["no-modifiable-description"], iconLarge, null);
-						
-						this.getElements().each(function(element) {element.disable();});
-					}
-					else
-					{
-						this._doReload();
-					}
-				}
-			}
-			// Too many pages selected
-			else
-			{
-				this._currentPagesId = [];
-				this._doReload();
-			}
-			
-			break;
-		}
-	}
-	
-	for (var i=messages.length - 1; i >= 0; i--)
-	{
-		if (messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_METADATACHANGED
-	        || messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_MINORMETADATACHANGED)
-		{
-			this._doReload();
-			break;
-		}
-	}
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._convertToId = function (targets)
-{
-	var targetsIds = [];
-	for (var i = 0; i < targets.length; i++)
-	{
-		targetsIds.push(targets[i].getParameters()['id']);
-	}
-	return targetsIds;
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype.checkTarget = function ()
-{
-	return function (target) { return target.getType() == 'page'; };
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._handlingMultiple = function(tooltip, paramPrefix, params)
-{
-	var count = parseInt(params[paramPrefix + "-size"]);
-	if (count > 0)
-	{
-		if (tooltip != "")
-		{
-			tooltip += "<br/><br/>";
-		}
-		
-		for (var i=0; i < count; i++)
-		{
-			if (i != 0) 
-			{
-				tooltip += "<br/>";
-			}
-			
-			tooltip += this.getConfiguration()[paramPrefix + "-start-description"];
-			tooltip += params[paramPrefix + "-" + i];
-			tooltip += this.getConfiguration()[paramPrefix + "-end-description"];
-			
-			if (params["scheduled-page-startdate-" + i] != null)
-			{
-				tooltip += "<br/>";
-				tooltip += this.getConfiguration()["scheduled-page-startdate-start-description"];
-				tooltip += params["scheduled-page-startdate-" + i];
-				tooltip += this.getConfiguration()["scheduled-page-startdate-end-description"];
-			}
-			
-			if (params["scheduled-page-enddate-" + i] != null)
-			{
-				tooltip += "<br/>";
-				tooltip += this.getConfiguration()["scheduled-page-enddate-start-description"];
-				tooltip += params["scheduled-page-enddate-" + i];
-				tooltip += this.getConfiguration()["scheduled-page-enddate-end-description"];
-			}
-			
-		}
-	}
-	
-	return tooltip;
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype._getIcon = function(params)
-{
-	var nbScheduledPages = parseInt(params['scheduled-page-size']);
-	var nbValidPages = parseInt(params['scheduled-page-valid-size']);
-	var nbOutOfDatePages = parseInt(params['scheduled-page-outofdate-size']);
-	var nbForthComingPages = parseInt(params['scheduled-page-forthcoming-size']);
-	
-	if (nbScheduledPages > 0 && nbValidPages > 0)
-	{
-		var iconSmall = context.contextPath + this.getConfiguration()['on-icon-small'];
-		var iconLarge = context.contextPath + this.getConfiguration()['on-icon-medium'];
-		this.getElements().each(
-			function (element) 
-			{
-				element.iconSmall = iconSmall;
-				element.iconLarge = iconLarge;
-			}
-		);
-	}
-	
-	if (nbScheduledPages > 0 && nbForthComingPages > 0)
-	{
-		var iconSmall = context.contextPath + this.getConfiguration()['forthcoming-icon-small'];
-		var iconLarge = context.contextPath + this.getConfiguration()['forthcoming-icon-medium'];
-		this.getElements().each(
-			function (element) 
-			{
-				element.iconSmall = iconSmall;
-				element.iconLarge = iconLarge;
-			}
-		);
-	}
-	
-	if (nbScheduledPages > 0 && nbOutOfDatePages > 0)
-	{
-		var iconSmall = context.contextPath + this.getConfiguration()['outofdate-icon-small'];
-		var iconLarge = context.contextPath + this.getConfiguration()['outofdate-icon-medium'];
-		this.getElements().each(
-			function (element) 
-			{
-				element.iconSmall = iconSmall;
-				element.iconLarge = iconLarge;
-			}
-		);
-	}
-	
-	if (nbScheduledPages == 0)
-	{
-		var iconSmall = context.contextPath + this.getConfiguration()['icon-small'];
-		var iconLarge = context.contextPath + this.getConfiguration()['icon-medium'];
-		this.getElements().each(
-			function (element) 
-			{
-				element.iconSmall = iconSmall;
-				element.iconLarge = iconLarge;
-			}
-		);
-	}
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype.configurationError = function(response)
-{
-	this.getElements().each(function(element) { element.stopRefreshing(); element.setDisabled(true); });
-	this.updateTooltip(this.getConfiguration()["default-description"]);
-}
-
-org.ametys.ribbon.control.button.ScheduleActionButton.prototype.configurationChanged = function(params)
-{
-	this.getElements().each(function(element) { element.stopRefreshing(); });
-	
-	var tooltip = this._handlingMultiple(this.getConfiguration()["default-description"], "scheduled-page-valid", params);
-	tooltip = this._handlingMultiple(tooltip, "scheduled-page-outofdate", params);
-	tooltip = this._handlingMultiple(tooltip, "scheduled-page-forthcoming", params);
-	
-	var isDisabled = params['scheduled-page-norights-size'] != '0'; 
-	if (isDisabled)
-	{
-		tooltip += "<br/><br/>";
-		tooltip += this.getConfiguration()["norights-description"];
-	}
-	
-	// Icon
-	this._getIcon(params);
-	
-	this.getElements().each(function(element) { element.updateIcon(); });
-	this.updateTooltip(tooltip, this.getElements().get(0).iconLarge, null);
-	
-	var nbScheduledPages = parseInt(params['scheduled-page-size']);
-	this.getElements().each(function(element) { element.toggle(nbScheduledPages > 0); });	
-	this.getElements().each(function(element) { element.setDisabled(isDisabled); });
-	
-	this._lastParams = params;
-}
Index: main/plugin-web/resources/js/org/ametys/web/page/SchedulePublication.js
===================================================================
--- main/plugin-web/resources/js/org/ametys/web/page/SchedulePublication.js	(revision 32509)
+++ main/plugin-web/resources/js/org/ametys/web/page/SchedulePublication.js	(nonexistent)
@@ -1,206 +0,0 @@
-/*
- *  Copyright 2010 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.web.page');
-
-org.ametys.web.page.SchedulePublication = function (controlId, controlConfiguration, controlPluginName)
-{
-	this._pluginName = controlPluginName;
-	var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
-	
-	var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function (target) { return target.getType() == 'page';});
-	if (target == null)
-	{
-		return;
-	}
-	
-	org.ametys.web.page.SchedulePublication._targetPageId = target.getParameters().id;
-	
-	if (!org.ametys.web.page.SchedulePublication.delayedInitialize ())
-		return;
-	
-	org.ametys.web.page.SchedulePublication._initForm ();
-	
-	org.ametys.web.page.SchedulePublication._box.show();
-}
-
-/**
- * @private
- * The id of the current selected page
- */
-org.ametys.web.page.SchedulePublication._targetPageId;
-
-org.ametys.web.page.SchedulePublication.delayedInitialize = function ()
-{
-	if (org.ametys.web.page.SchedulePublication._initialized)
-		return true;
-	
-	// start date
-	org.ametys.web.page.SchedulePublication._startDateField = new org.ametys.form.DateField ({
-		fieldLabel: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_START_DATE"/>",
-        desc: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_START_DATE_DESC"/>",
-        name: 'page.input.date.single.publicationStartDate',
-        listeners: {'afterrender': function() { this.setWidth('auto'); this.setWidth(this.initialConfig.width); }},
-        width: 150
-	});
-	
-	// start hour (TODO)
-	org.ametys.web.page.SchedulePublication._hourField = new org.ametys.form.TimeField ({
-		fieldLabel: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_HOUR"/>",
-		desc: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_HOUR_DESC"/>",
-        name: 'hour',
-        increment: 60,
-        editable: true,
-        value: '8:00',
-        width: 80
-	});
-	
-	// end date
-	org.ametys.web.page.SchedulePublication._endDateField = new org.ametys.form.DateField ({
-		fieldLabel: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_END_DATE"/>",
-        desc: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_END_DATE_DESC"/>",
-        name: 'page.input.date.single.publicationEndDate',
-        listeners: {'afterrender': function() { this.setWidth('auto'); this.setWidth(this.initialConfig.width); }},
-		width: 150
-	});
-	
-	org.ametys.web.page.SchedulePublication._box = new org.ametys.DialogBox ({
-		
-		title : "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_TITLE"/>",
-		icon : getPluginResourcesUrl('web') + "/img/publication/publication_16.png",
-		
-		width: 380,
-		height: 210,
-		autoScroll: true,
-		cls: 'text-dialog',
-
-		layout: 'form',
-		labelWidth :80,
-		
-		items : [ new org.ametys.HtmlContainer ({ html: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_HINT"/>", cls: 'dialog-text-hint'}),
-				  org.ametys.web.page.SchedulePublication._startDateField,
-				  org.ametys.web.page.SchedulePublication._endDateField
-		          // org.ametys.web.page.SchedulePublication._hourField
-		],
-		
-		defaultButton: org.ametys.web.page.SchedulePublication._startDateField,
-		closeAction: 'hide',
-		buttons : [{
-			text: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_OK_BTN"/>",
-			handler: org.ametys.web.page.SchedulePublication.ok
-		}, {
-			text: "<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_DIALOG_CANCEL_BTN"/>",
-			handler : org.ametys.web.page.SchedulePublication.cancel
-		} ]
-	});
-	
-	org.ametys.web.page.SchedulePublication._initialized = true;
-	
-	return true;
-}
-
-
-org.ametys.web.page.SchedulePublication._initForm = function ()
-{
-	var serverMessage = new org.ametys.servercomm.ServerMessage('web', '/page/publication-dates', {'id': org.ametys.web.page.SchedulePublication._targetPageId}, org.ametys.servercomm.ServerComm.PRIORITY_SYNCHRONOUS, null, this, null);
-	var response = org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
-	
-	if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR"/>", response, "org.ametys.web.page.SchedulePublication"))
-	{
-		return;
-	}
-	
-	var startDate = response.selectSingleNode('page/start-date') != null ? response.selectSingleNode('page/start-date')[org.ametys.servercomm.ServerComm.xmlTextContent] : '';
-	var endDate = response.selectSingleNode('page/end-date') != null ? response.selectSingleNode('page/end-date')[org.ametys.servercomm.ServerComm.xmlTextContent] : '';
-	
-	if (startDate != '')
-	{
-		org.ametys.web.page.SchedulePublication._startDateField.setValue(Date.parseDate (startDate, Date.patterns.ISO8601Long));
-	}
-	else
-	{
-		org.ametys.web.page.SchedulePublication._startDateField.setValue('');
-	}
-	
-	if (endDate != '')
-	{
-		org.ametys.web.page.SchedulePublication._endDateField.setValue(Date.parseDate (endDate, Date.patterns.ISO8601Long));
-	}
-	else
-	{
-		org.ametys.web.page.SchedulePublication._endDateField.setValue('');
-	}
-}
-
-org.ametys.web.page.SchedulePublication.ok = function()
-{
-	var params = org.ametys.web.page.SchedulePublication._getParams ();
-	if (params == null)
-	{
-		return;
-	}
-	
-	var serverMessage = new org.ametys.servercomm.ServerMessage("web", 'repository/edit-metadata', params, org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, org.ametys.web.page.SchedulePublication._saveCallBack, this, params, "xml");
-	org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
-}
-
-
-org.ametys.web.page.SchedulePublication._saveCallBack = function (response, params)
-{
-	if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_ERROR"/>", response, "org.ametys.web.page.SchedulePublication"))
-	{
-		return;
-	}
-	
-	org.ametys.web.page.SchedulePublication._box.hide();
-	
-	var target = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("page", {'id': params.id});
-	var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_MINORMETADATACHANGED, null, [target]);
-	org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
-}
-
-org.ametys.web.page.SchedulePublication._getParams = function()
-{
-	if (!org.ametys.web.page.SchedulePublication._startDateField.isValid() || !org.ametys.web.page.SchedulePublication._endDateField.isValid())
-	{
-		return null;
-	}
-	
-	var params = {};
-	params['id'] = org.ametys.web.page.SchedulePublication._targetPageId;
-	
-	var startDate = org.ametys.web.page.SchedulePublication._startDateField.getValue();
-	var endDate = org.ametys.web.page.SchedulePublication._endDateField.getValue();
-	// var hour = org.ametys.web.page.SchedulePublication._hourField.getValue().split(':');
-	// startDate = startDate(Date.HOUR, parseInt(hour[0], 10)).add(Date.MINUTE, parseInt(hour[1], 10));
-	
-	if (endDate != '' && startDate != '' && endDate.getTime() - startDate.getTime() <= 0)
-	{
-		org.ametys.web.page.SchedulePublication._endDateField.markInvalid ("<i18n:text i18n:key="PLUGINS_WEB_PUBLICATION_SCHEDULE_INTERVAL_ERROR"/>");
-		return;
-	}
-	
-	params[org.ametys.web.page.SchedulePublication._startDateField.getName()] = startDate != '' ? startDate.format (Date.patterns.ISO8601Long) : '';
-	params[org.ametys.web.page.SchedulePublication._endDateField.getName()] = endDate != '' ? endDate.format (Date.patterns.ISO8601Long) : '';
-	
-	return params;
-}
-
-org.ametys.web.page.SchedulePublication.cancel = function ()
-{
-	org.ametys.web.page.SchedulePublication._box.hide();
-}
-
Index: main/plugin-web/sitemap-back.xmap
===================================================================
--- main/plugin-web/sitemap-back.xmap	(revision 32549)
+++ main/plugin-web/sitemap-back.xmap	(working copy)
@@ -49,7 +48,6 @@
 			<map:generator name="common-available-ctypes" src="org.ametys.web.contenttype.CommonAvailablesContentTypesGenerator"/>
             <map:generator name="lock" src="org.ametys.web.lock.UnlockGenerator" logger="org.ametys.web.lock.UnlockGenerator"/>
             
-			<map:generator name="publication-dates" src="org.ametys.web.repository.page.generators.PublicationDatesGenerator"/>
 			<map:generator name="search-validated-contents" src="org.ametys.web.search.SearchValidatedContentsGenerator"/>
 			<map:generator name="last-publication" src="org.ametys.web.workflow.GetLastPublishedContents"/>
 			
@@ -117,9 +115,7 @@
             
             <map:action name="administrator-is-system-user" src="org.ametys.runtime.plugins.admin.IsAdminAction" logger="org.ametys.runtime.plugins.web.administrator.sitemap.action.isAdmin"/>
 
-		  	<map:action name="disallow-robots" src="org.ametys.web.repository.page.actions.DisallowRobotsAction"/>
 		  	<map:action name="tag" src="org.ametys.web.repository.tag.actions.TagAction"/>
-		  	<map:action name="edit-metadata" src="org.ametys.web.repository.page.actions.EditMetadataAction"/>
 
 		  	<map:action name="page-path" src="org.ametys.web.repository.page.actions.GetPagePathAction"/>
 		  	
@@ -448,13 +437,6 @@
 				</map:act>
 			</map:match>
 			
-			<map:match pattern="repository/edit-metadata">
-				<map:act type="edit-metadata">
-					<map:generate type="action-result"/>
-					<map:serialize type="xml"/>
-				</map:act>
-			</map:match>
-			
 			<map:match pattern="repository/page-info">
 				<map:generate type="page-info"/>
 				<map:serialize type="xml"/>
@@ -475,24 +457,11 @@
 				<map:serialize type="xml"/>
 			</map:match>
 			
-			<map:match pattern="page/properties">
-				<map:generate type="page-properties"/>
-				<map:transform type="i18n">
-					<map:parameter name="locale" value="{locale:locale}"/>
-				</map:transform>
-				<map:serialize type="xml"/>
-			</map:match>
-			
 			<map:match pattern="pages/filter-regexp">
 				<map:generate type="filter-pages-by-regexp"/>
 				<map:serialize type="xml"/>
 			</map:match>
 			
-			<map:match pattern="page/publication-dates">
-				<map:generate type="publication-dates"/>
-				<map:serialize type="xml"/>
-			</map:match>
-			
 			<map:match pattern="repository/generate-site">
 				<map:act type="generate-site" src="{request-param:siteId}">
 					<map:generate type="action-result">
Index: main/plugin-web/src/org/ametys/web/clientsideelement/ScheduledPageClientSideElement.java
===================================================================
--- main/plugin-web/src/org/ametys/web/clientsideelement/ScheduledPageClientSideElement.java	(revision 32542)
+++ main/plugin-web/src/org/ametys/web/clientsideelement/ScheduledPageClientSideElement.java	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2010 Anyware Services
+ *  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.
@@ -31,7 +31,9 @@
 import org.apache.cocoon.components.ContextHelper;
 import org.apache.cocoon.i18n.I18nUtils;
 
+import org.ametys.core.ui.Callable;
 import org.ametys.core.util.I18nizableText;
+import org.ametys.runtime.parameter.ParameterHelper;
 import org.ametys.web.repository.page.Page;
 import org.ametys.web.repository.page.jcr.DefaultPage;
 
@@ -53,6 +55,8 @@
      * @param pageIds The page ids
      * @return the page status
      */
+    @SuppressWarnings("unchecked")
+    @Callable
     public Map<String, Object> getStatus (List<String> pageIds)
     {
         Map<String, Object> results = new HashMap<>();
@@ -147,6 +151,38 @@
         return results;
     }
     
+    /**
+     * Get the publication dates of a page
+     * @param pageId the id of the page
+     * @return a map
+     */
+    @Callable
+    public Map<String, Object> getPublicationDates(String pageId)
+    {
+        Map<String, Object> result = new HashMap<> ();
+        Map<String, String> pageMap = new HashMap<> ();
+        
+        Page page = _resolver.resolveById(pageId);
+        
+        result.put("id", pageId);
+        
+        
+        Date startDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_START_DATE, null);
+        if (startDate != null)
+        {
+            pageMap.put("start-date", ParameterHelper.valueToString(startDate));
+        }
+        
+        Date endDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_END_DATE, null);
+        if (endDate != null)
+        {
+            pageMap.put("end-date", ParameterHelper.valueToString(endDate));
+        }
+        
+        result.put("page", pageMap);
+        return result;
+    }
+    
     private I18nizableText _getDateValidDescription (Page page)
     {
         List<String> i18nParameters = new ArrayList<>();
Index: main/plugin-web/src/org/ametys/web/repository/page/PageDAO.java
===================================================================
--- main/plugin-web/src/org/ametys/web/repository/page/PageDAO.java	(revision 32530)
+++ main/plugin-web/src/org/ametys/web/repository/page/PageDAO.java	(working copy)
@@ -16,9 +16,11 @@
 package org.ametys.web.repository.page;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.avalon.framework.component.Component;
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
@@ -29,15 +31,19 @@
 
 import org.ametys.core.observation.Event;
 import org.ametys.core.observation.ObservationManager;
-import org.ametys.core.right.RightsManager;
 import org.ametys.core.ui.Callable;
 import org.ametys.core.user.CurrentUserProvider;
 import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
+import org.ametys.runtime.parameter.ParameterHelper;
+import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
 import org.ametys.web.ObservationConstants;
 import org.ametys.web.repository.page.Page.LinkType;
 import org.ametys.web.repository.page.Page.PageType;
-import org.ametys.web.repository.site.SiteManager;
-import org.ametys.web.sitemap.SitemapDecoratorsHandler;
+import org.ametys.web.skin.SkinTemplate;
+import org.ametys.web.skin.SkinTemplateZone;
+import org.ametys.web.skin.SkinsManager;
+import org.ametys.web.skin.TemplatesAssignmentHandler;
 
 /**
  * DAO for manipulating pages
@@ -45,24 +51,23 @@
  */
 public class PageDAO extends AbstractLogEnabled implements Serviceable, Component
 {
+    /** Prefix for form elements. */
+    public static final String FORM_ELEMENTS_PREFIX = "page.input.";
+    
     private AmetysObjectResolver _resolver;
-    private RightsManager _rightsManager;
     private ObservationManager _observationManager;
     private CurrentUserProvider _currentUserProvider;
-    private CopySiteComponent _copySiteComponent;
-    private SiteManager _siteManager;
-    private SitemapDecoratorsHandler _sitemapHandler;
+    private SkinsManager _skinsManager;
+    private TemplatesAssignmentHandler _templatesHandler;
     
     @Override
     public void service(ServiceManager smanager) throws ServiceException
     {
         _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
-        _rightsManager = (RightsManager) smanager.lookup(RightsManager.ROLE);
         _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
         _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
-        _copySiteComponent = (CopySiteComponent) smanager.lookup(CopySiteComponent.ROLE);
-        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
-        _sitemapHandler = (SitemapDecoratorsHandler) smanager.lookup(SitemapDecoratorsHandler.ROLE);
+        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
+        _templatesHandler = (TemplatesAssignmentHandler) smanager.lookup(TemplatesAssignmentHandler.ROLE);
     }
     
     /**
@@ -141,8 +146,6 @@
     /**
      * Set pages as blank page
      * @param pageIds the id of pages to modify
-     * @param url the url of redirection
-     * @param urlType the type of redirection
      * @return the id of pages which succeeded or failed. 
      */
     @Callable
@@ -199,5 +202,400 @@
         
         return result;
     }
+    
+    /**
+     * Set a template to pages
+     * @param pageIds the id of pages to update
+     * @param templateName The template name
+     * @return the id of pages which succeeded
+     */
+    @Callable
+    public Map<String, Object> setTemplate (List<String> pageIds, String templateName)
+    {
+        Map<String, Object> result = new HashMap<String, Object>();
+        
+        List<String> successes = new ArrayList<String>();
+        
+        String defaultZoneName = null;
+        
+        for (String pageId : pageIds)
+        {
+            Page page = _resolver.resolveById(pageId);
+            if (!(page instanceof ModifiablePage))
+            {
+                throw new IllegalArgumentException("Can not set template a non-modifiable page " + pageId);
+            }
+            
+            ModifiablePage mPage = (ModifiablePage) page;
+            
+            if (defaultZoneName == null)
+            {
+                String skinId = page.getSite().getSkinId();
+                SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(templateName);
+                if (tpl == null)
+                {
+                    throw new IllegalStateException("Template '" + templateName + "' does not exist on skin '" + skinId + "'");
+                }
+                
+                defaultZoneName = tpl.getDefaultZoneId();
+            }
+            
+            if (!_templatesHandler.getAvailablesTemplates(mPage).contains(templateName))
+            {
+                throw new IllegalStateException("Template '" + templateName + "' is not available for page '" + pageId + "'");
+            }
+            
+            if (page.getType().equals(PageType.CONTAINER))
+            {
+                _removeOldZones (mPage, templateName);
+            }
+            
+            mPage.setTemplate(templateName);
+            mPage.setType(PageType.CONTAINER);
+            mPage.getSitemap().saveChanges();
+            
+            successes.add(pageId);
+            
+            Map<String, Object> eventParams = new HashMap<>();
+            eventParams.put(ObservationConstants.ARGS_PAGE, page);
+            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
+        }
+        
+        if (defaultZoneName != null)
+        {
+            result.put("zonename", defaultZoneName);
+        }
+        
+        result.put("success", successes);
+        
+        return result;
+    }
+    
+    private void _removeOldZones (ModifiablePage page, String templateName)
+    {
+        String skinId = page.getSite().getSkinId();
+        
+        SkinTemplate oldTemplate = _skinsManager.getSkin(skinId).getTemplate(templateName);
+        
+        Map<String, SkinTemplateZone> templateZones = oldTemplate.getZones();
+        
+        for (ModifiableZone zone : page.getZones())
+        {
+            if (!templateZones.containsKey(zone.getName()))
+            {
+                zone.remove();
+            }
+        }
+    }
 
+    
+    /**
+     * Set metadata on a page
+     * @param pageId the id of the page
+     * @param values the values of the metadata to set
+     * @return an empty map
+     */
+    @Callable
+    public Map setMetadata(String pageId, Map<String, List<String>> values) 
+    {
+        ModifiablePage page = _resolver.resolveById(pageId);
+        
+        // FIXME API
+        ModifiableCompositeMetadata metaHolder = page.getMetadataHolder();
+
+        Set<String> paramNames = values.keySet();
+        for (String paramName : paramNames)
+        {
+            if (paramName.startsWith(FORM_ELEMENTS_PREFIX))
+            {
+                List<String> metadataValues = values.get(paramName);
+                
+                // ex: page.input.string.multiple.keywords.title
+                // ex: page.input.date.single.publication.date
+                paramName = paramName.substring(FORM_ELEMENTS_PREFIX.length());
+                
+                String[] a = paramName.split("\\.");
+                String type = a[0];
+                boolean isMultiple = a[1].equals("multiple");
+                String[] metadataPath = new String[a.length - 2];
+                for (int i = 0; i < metadataPath.length; i++)
+                {
+                    metadataPath[i] = a[i + 2];
+                }
+                
+                _saveMetadata(metaHolder, metadataPath, type, metadataValues, isMultiple);
+            }
+        }
+        
+        if (page.needsSave())
+        {
+            page.saveChanges();
+        }
+        
+        // Notify observer
+        Map<String, Object> eventParams = new HashMap<>();
+        eventParams.put(ObservationConstants.ARGS_PAGE, page);
+        _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
+        
+        return java.util.Collections.EMPTY_MAP;
+    }
+    
+    private void _saveMetadata (ModifiableCompositeMetadata metadata, String[] metadataPath, String type, List<String> values, boolean isMultiple)
+    {
+        if (metadataPath.length == 1)
+        {
+            _saveMetadata(metadata, metadataPath[0], type, values, isMultiple);
+        }
+        else
+        {
+            String[] childPath = new String[metadataPath.length - 1];
+            for (int i = 0; i < childPath.length; i++)
+            {
+                childPath[i] = metadataPath[i + 1];
+            }
+            ModifiableCompositeMetadata compMetadata = metadata.getCompositeMetadata(metadataPath[0], true);
+            _saveMetadata (compMetadata, childPath, type, values, isMultiple);
+        }
+    }
+    
+    private void _saveMetadata (ModifiableCompositeMetadata metadata, String metadataName, String type, List<String> values, boolean isMultiple)
+    {
+        ParameterType paramType = ParameterHelper.stringToType(type);
+        
+        if (ParameterType.STRING.equals(paramType))
+        {
+            _saveStringMetadata (metadata, metadataName, values, isMultiple);
+        }
+        else if (ParameterType.DATE.equals(paramType))
+        {
+            _saveDateMetadata (metadata, metadataName, values, isMultiple);
+        }
+        else if (ParameterType.LONG.equals(paramType))
+        {
+            _saveLongMetadata (metadata, metadataName, values, isMultiple);
+        }
+        else if (ParameterType.DOUBLE.equals(paramType))
+        {
+            _saveDoubleMetadata (metadata, metadataName, values, isMultiple);
+        }
+        else if (ParameterType.BOOLEAN.equals(paramType))
+        {
+            _saveBooleanMetadata (metadata, metadataName, values, isMultiple);
+        }
+        else if (ParameterType.BINARY.equals(paramType))
+        {
+            // TODO
+        }
+    }
+    
+    private void _saveStringMetadata (ModifiableCompositeMetadata metadata, String metadataName, List<String> values, boolean isMultiple)
+    {
+        List<String> stringValues = new ArrayList<String>();
+        
+        for (String value : values)
+        {
+            if (!StringUtils.isEmpty(value))
+            {
+                stringValues.add(value);
+            }
+        }
+        
+        String[] stringArray = stringValues.toArray(new String[stringValues.size()]);
+        if (stringArray.length == 0)
+        {
+            if (metadata.hasMetadata(metadataName))
+            {
+                metadata.removeMetadata(metadataName);
+            }
+        }
+        else
+        {
+            if (isMultiple)
+            {
+                metadata.setMetadata(metadataName, stringArray);
+            }
+            else
+            {
+                metadata.setMetadata(metadataName, stringArray[0]);
+            }
+        }
+    }
+    
+    private void _saveDateMetadata (ModifiableCompositeMetadata metadata, String metadataName, List<String> values, boolean isMultiple)
+    {
+        List<Date> dateValues = new ArrayList<Date>();
+        
+        for (String value : values)
+        {
+            if (!"".equals(value))
+            {
+                Date date = (Date) ParameterHelper.castValue(value, ParameterType.DATE);
+                if (date == null)
+                {
+                    getLogger().error("Cannot cast value '" + value + "' into type 'date'");
+                    throw new IllegalArgumentException("Invalid format for date value '" +  value + "'");
+                }
+                dateValues.add(date);
+            }
+        }
+        
+        Date[] dateArray = dateValues.toArray(new Date[dateValues.size()]);
+        
+        if (dateArray.length == 0)
+        {
+            if (metadata.hasMetadata(metadataName))
+            {
+                metadata.removeMetadata(metadataName);
+            }
+        }
+        else
+        {
+            if (isMultiple)
+            {
+                metadata.setMetadata(metadataName, dateArray);
+            }
+            else
+            {
+                metadata.setMetadata(metadataName, dateArray[0]);
+            }
+        }
+    }
+    
+    private void _saveLongMetadata (ModifiableCompositeMetadata metadata, String metadataName, List<String> values, boolean isMultiple)
+    {
+        List<Long> longValues = new ArrayList<Long>();
+        
+        for (String value : values)
+        {
+            if (!"".equals(value))
+            {
+                try
+                {
+                    longValues.add(Long.parseLong(value));
+                }
+                catch (NumberFormatException e)
+                {
+                    getLogger().error("Cannot cast value '" + value + "' into type 'long'", e);
+                    throw new IllegalArgumentException("Invalid format for long value '" +  value + "'", e);
+                }
+            }
+        }
+        
+        long[] longArray = new long[longValues.size()];
+        int i = 0;
+        for (Long l : longValues)
+        {
+            longArray[i] = l.longValue();
+            i++;
+        }
+        
+        if (longArray.length == 0)
+        {
+            if (metadata.hasMetadata(metadataName))
+            {
+                metadata.removeMetadata(metadataName);
+            }
+        }
+        else
+        {
+            if (isMultiple)
+            {
+                metadata.setMetadata(metadataName, longArray);
+            }
+            else
+            {
+                metadata.setMetadata(metadataName, longArray[0]);
+            }
+        }
+        
+    }
+    
+    private void _saveDoubleMetadata (ModifiableCompositeMetadata metadata, String metadataName, List<String> values, boolean isMultiple)
+    {
+        List<Double> doubleValues = new ArrayList<Double>();
+        
+        for (String value : values)
+        {
+            if (!"".equals(value))
+            {
+                try
+                {
+                    doubleValues.add(Double.parseDouble(value));
+                }
+                catch (NumberFormatException e)
+                {
+                    getLogger().error("Cannot cast value '" + value + "' into type 'double'", e);
+                    throw new IllegalArgumentException("Invalid format for double value '" +  value + "'", e);
+                }
+            }
+        }
+        
+        double[] doubleArray = new double[doubleValues.size()];
+        int i = 0;
+        for (Double d : doubleValues)
+        {
+            doubleArray[i] = d.doubleValue();
+            i++;
+        }
+        
+        
+        if (doubleArray.length == 0)
+        {
+            if (metadata.hasMetadata(metadataName))
+            {
+                metadata.removeMetadata(metadataName);
+            }
+        }
+        else
+        {
+            if (isMultiple)
+            {
+                metadata.setMetadata(metadataName, doubleArray);
+            }
+            else
+            {
+                metadata.setMetadata(metadataName, doubleArray[0]);
+            }
+        }
+    }
+    
+    private void _saveBooleanMetadata (ModifiableCompositeMetadata metadata, String metadataName, List<String> values, boolean isMultiple)
+    {
+        List<Boolean> booleanValues = new ArrayList<Boolean>();
+        
+        for (String value : values)
+        {
+            if (!"".equals(value))
+            {
+                booleanValues.add(Boolean.parseBoolean(value));
+            }
+        }
+        
+        boolean[] booleanArray = new boolean[booleanValues.size()];
+        int i = 0;
+        for (Boolean b : booleanValues)
+        {
+            booleanArray[i] = b.booleanValue();
+            i++;
+        }
+        
+        if (booleanArray.length == 0)
+        {
+            if (metadata.hasMetadata(metadataName))
+            {
+                metadata.removeMetadata(metadataName);
+            }
+        }
+        else
+        {
+            if (isMultiple)
+            {
+                metadata.setMetadata(metadataName, booleanArray);
+            }
+            else
+            {
+                metadata.setMetadata(metadataName, booleanArray[0]);
+            }
+        }
+    }
 }
