Index: main/plugin-odf-web/plugin.xml
===================================================================
--- main/plugin-odf-web/plugin.xml	(revision 38750)
+++ main/plugin-odf-web/plugin.xml	(working copy)
@@ -678,7 +678,7 @@
                     <parameter name="orgunitlist" type="string" multiple="true">
                         <label i18n="true">PLUGINS_ODFWEB_SERVICE_PROGRAM_LIST_ORGUNIT_LIST</label>
                         <description i18n="true">PLUGINS_ODFWEB_SERVICE_PROGRAM_LIST_ORGUNIT_LIST_DESC</description>
-                        <widget>orgunit</widget>
+                        <widget>orgunit-web</widget>
                         <enumeration>
                             <custom-enumerator class="org.ametys.odf.orgunit.OrgUnitEnumerator"/>
                         </enumeration>
@@ -865,6 +865,10 @@
                        class="org.ametys.plugins.odfweb.observation.InvalidateCacheOnOrgunitValidationObserver"/>
             
             <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
+                       id="org.ametys.plugins.odfweb.observation.InvalidateCacheOnOrgunitMovedObserver"
+                       class="org.ametys.plugins.odfweb.observation.InvalidateCacheOnOrgunitMovedObserver"/>
+            
+            <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
                        id="org.ametys.plugins.odfweb.observation.LuceneContentValidatedObserver"
                        class="org.ametys.plugins.odfweb.observation.LuceneContentValidatedObserver"/>
                        
@@ -876,6 +880,22 @@
           			   id="org.ametys.plugins.odfweb.observation.LuceneODFRootDeletedObserver" 
           			   class="org.ametys.plugins.odfweb.observation.LuceneODFRootDeletedObserver"/>	
                        
+          	<extension point="org.ametys.cms.observation.ObserverExtensionPoint" 
+          			   id="org.ametys.plugins.odfweb.observation.LuceneOnSiteConfModifiedPart1Observer" 
+          			   class="org.ametys.plugins.odfweb.observation.LuceneOnSiteConfModifiedPart1Observer"/>	
+                       
+          	<extension point="org.ametys.cms.observation.ObserverExtensionPoint" 
+          			   id="org.ametys.plugins.odfweb.observation.LuceneOnSiteConfModifiedPart2Observer" 
+          			   class="org.ametys.plugins.odfweb.observation.LuceneOnSiteConfModifiedPart2Observer"/>	
+                       
+          	<extension point="org.ametys.cms.observation.ObserverExtensionPoint" 
+          			   id="org.ametys.plugins.odfweb.observation.LuceneOrgUnitMovedPart1Observer" 
+          			   class="org.ametys.plugins.odfweb.observation.LuceneOrgUnitMovedPart1Observer"/>	
+                       
+          	<extension point="org.ametys.cms.observation.ObserverExtensionPoint" 
+          			   id="org.ametys.plugins.odfweb.observation.LuceneOrgUnitMovedPart2Observer" 
+          			   class="org.ametys.plugins.odfweb.observation.LuceneOrgUnitMovedPart2Observer"/>	
+                       
             <extension point="org.ametys.cms.observation.ObserverExtensionPoint" 
                        id="org.ametys.plugins.odfweb.observation.ClearODFPageCacheObserver" 
                        class="org.ametys.plugins.odfweb.observation.ClearODFPageCacheObserver"/>      	 
@@ -1125,4 +1145,18 @@
                        class="org.ametys.plugins.odfweb.WebSubProgramMetadataIndexer"/>
         </components>
     </feature>
+    
+    <feature name="odf-web.widgets">
+        <extensions>
+            <extension id="org.ametys.plugins.odfweb.Widgets"
+                       point="org.ametys.cms.workspace.uitool.StaticFileImportsManager"
+                       class="org.ametys.cms.workspace.uitool.StaticFileImportsClientSideElement">
+                <scripts>
+                    <file plugin="odf">js/org/ametys/odf/widgets/OrgUnitWidget.i18n.js</file>
+                    <file>js/org/ametys/odf/web/widgets/OrgUnitWebWidget.i18n.js</file>
+                </scripts>
+            </extension>
+        </extensions>
+    </feature>
+    
 </plugin>
Index: main/plugin-odf-web/resources/js/org/ametys/odf/web/widgets/OrgUnitWebWidget.i18n.js
===================================================================
--- main/plugin-odf-web/resources/js/org/ametys/odf/web/widgets/OrgUnitWebWidget.i18n.js	(revision 0)
+++ main/plugin-odf-web/resources/js/org/ametys/odf/web/widgets/OrgUnitWebWidget.i18n.js	(revision 0)
@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2016 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.odf.web.widgets');
+
+//Register widget
+org.ametys.utils.Widgets.registerWidget ('string', 'orgunit-web', 'org.ametys.odf.web.widgets.OrgUnitWebWidget');
+
+
+org.ametys.odf.web.widgets.OrgUnitWebWidget = function (config)
+{
+    org.ametys.odf.web.widgets.OrgUnitWebWidget.superclass.constructor.call (this, config);
+}
+
+Ext.extend(org.ametys.odf.web.widgets.OrgUnitWebWidget, org.ametys.odf.widgets.OrgUnitWidget, {});
+
+org.ametys.odf.web.widgets.OrgUnitWebWidget.prototype.selectOrgUnit = function ()
+{
+    // Verify if the ODF is limited to a specific component.
+    var serverMessage = new org.ametys.servercomm.ServerMessage('odf-web', '/' + context.parameters.siteName + '/get-display-orgunit', {}, org.ametys.servercomm.ServerComm.PRIORITY_SYNCHRONOUS, null, this, null);
+    var response = org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+    var ouRootId = response.selectSingleNode('ActionResult/ouRootId')[org.ametys.servercomm.ServerComm.xmlTextContent];
+    
+    if (!ouRootId)
+    {
+        org.ametys.odf.web.widgets.OrgUnitWebWidget.superclass.selectOrgUnit.call(this);
+    }
+    else
+    {
+        org.ametys.odf.helper.ChooseOrgUnit (
+                getPluginResourcesUrl('odf') + '/img/widgets/orgunit_16.png',
+                "<i18n:text i18n:key="PLUGINS_ODF_WIDGET_ORGUNITWIDGET_SELECTTITLE" catalogue="plugin.odf"/>", 
+                "<i18n:text i18n:key="PLUGINS_ODF_WIDGET_ORGUNITWIDGET_SELECTDESCRIPTION" catalogue="plugin.odf"/>",
+                this._selectOrgUnitCb.createDelegate(this), this.multiple,
+                this.getValue(),
+                ouRootId);
+    }
+    
+}
\ No newline at end of file
Index: main/plugin-odf-web/sitemap.xmap
===================================================================
--- main/plugin-odf-web/sitemap.xmap	(revision 38750)
+++ main/plugin-odf-web/sitemap.xmap	(working copy)
@@ -365,6 +365,14 @@
             <map:match pattern="indesign/download/*/*">
                 <map:read src="context://skins/{1}/indesign/{2}"/>
             </map:match>
+            
+            
+            <map:match pattern="*/get-display-orgunit">
+                <map:generate type="action-result">
+                    <map:parameter name="ouRootId" value="{site-configuration:{1}:display-orgunit-only}"/>
+                </map:generate>
+                <map:serialize type="xml"/>
+            </map:match>
         </map:pipeline>
     </map:pipelines>
 </map:sitemap>
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ProgramListGenerator.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ProgramListGenerator.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ProgramListGenerator.java	(working copy)
@@ -16,8 +16,8 @@
 package org.ametys.plugins.odfweb.generators;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -60,7 +60,6 @@
 import org.ametys.plugins.repository.query.expression.OrExpression;
 import org.ametys.plugins.repository.query.expression.StringExpression;
 import org.ametys.web.repository.page.Page;
-import org.ametys.web.repository.page.ZoneItemException;
 import org.ametys.web.site.SiteConfigurationExtensionPoint;
 
 /**
@@ -153,16 +152,16 @@
         contentHandler.startDocument();
         
         AttributesImpl atts = new AttributesImpl();
-        if (rootPage != null)
-        {
-            atts.addCDATAAttribute("root-page-path", rootPage.getPathInSitemap());
-        }
+        atts.addCDATAAttribute("root-page-path", rootPage.getPathInSitemap());
         
         XMLUtils.startElement(contentHandler, "programs", atts);
+        
+        HashMap<String, List<String>> metadataFilters = new HashMap<String, List<String>>(); 
        
         if (filteredByOrgUnits != null)
         {
-            ArrayList<String> filteredByOrgUnitsAsList = new ArrayList<String>(Arrays.asList(filteredByOrgUnits));
+            List<String> filteredByOrgUnitsAsList = Arrays.asList(filteredByOrgUnits);
+            metadataFilters.put("orgUnit", filteredByOrgUnitsAsList);
             
             for (Program program : programs)
             {
@@ -182,7 +181,7 @@
                 
                 if (found)
                 {
-                    saxProgram(program);
+                    saxProgram(program, metadataFilters);
                 }
             }
         }
@@ -190,7 +189,7 @@
         {
             for (Program program : programs)
             {
-                saxProgram(program);
+                saxProgram(program, metadataFilters);
             }            
         }
         XMLUtils.endElement(contentHandler, "programs");
@@ -211,7 +210,7 @@
             OrgUnit parentOrgUnit = ou.getParentOrgUnit();
             while (parentOrgUnit != null)
             {
-                if (parentOrgUnit.equals(filteredOU))
+                if (parentOrgUnit.getId().equals(filteredOU))
                 {
                     return true;
                 }
@@ -224,10 +223,11 @@
     /**
      * SAX a program.
      * @param program The program to SAX.
+     * @param metadataFilters Filters to apply to the metadatas
      * @throws SAXException If an error occurs while SAXing
      * @throws IOException If an error occurs while retrieving content.
      */
-    public void saxProgram(Program program) throws SAXException, IOException
+    public void saxProgram(Program program, HashMap<String, List<String>> metadataFilters) throws SAXException, IOException
     {
         CompositeMetadata metaHolder = program.getMetadataHolder();
         
@@ -256,7 +256,10 @@
             XMLUtils.startElement(contentHandler, "metadata", metadataAttrs);
             for (String value : values)
             {
-                XMLUtils.createElement(contentHandler, "value", value);
+                if (_filterMetadatas(metadataPath, value, metadataFilters))
+                {
+                    XMLUtils.createElement(contentHandler, "value", value);
+                }
             }
             XMLUtils.endElement(contentHandler, "metadata");
         }
@@ -273,6 +276,16 @@
         XMLUtils.endElement(contentHandler, "program");
     }
     
+    private boolean _filterMetadatas(String metadataPath, String value, HashMap<String, List<String>> metadataFilters)
+    {
+        if (metadataPath.equals("orgUnit") && metadataFilters.containsKey("orgUnit"))
+        {
+            OrgUnit ou = _ametysResolver.resolveById(value);
+            return _isEqualOrHasParentInList(ou, metadataFilters.get("orgUnit"));
+        }
+        return true;
+    }
+
     private String[] getMetadataValues(CompositeMetadata metaHolder, String metadataPath)
     {
         String filteredPath = StringUtils.strip(metadataPath, "/");
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOnSiteConfModifiedObserver.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOnSiteConfModifiedObserver.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOnSiteConfModifiedObserver.java	(revision 0)
@@ -0,0 +1,144 @@
+/*
+ *  Copyright 2016 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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.cms.repository.RequestAttributeWorkspaceSelector;
+import org.ametys.plugins.odfweb.repository.OdfPageHandler;
+import org.ametys.web.WebConstants;
+import org.ametys.web.lucene.AbstractLuceneObserver;
+import org.ametys.web.lucene.IndexTermCache;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.sitemap.Sitemap;
+
+/**
+ * {@link Observer} for observing site configuration modifications
+ * in order to synchronize lucene index associated.
+ */
+public abstract class AbstractLuceneOnSiteConfModifiedObserver extends AbstractLuceneObserver
+{
+    private OdfPageHandler _odfPageHandler;
+    private IndexTermCache _indexTermCache;
+
+    @Override
+    public void service(ServiceManager manager) throws ServiceException
+    {
+        super.service(manager);
+        _odfPageHandler = (OdfPageHandler) manager.lookup(OdfPageHandler.ROLE);
+        _indexTermCache = (IndexTermCache) manager.lookup(IndexTermCache.ROLE);
+    }
+
+    @Override
+    public boolean supports(Event event)
+    {
+        return event.getId().equals(org.ametys.web.ObservationConstants.SITE_UPDATED);
+    }
+
+    @Override
+    public void observe(Event event)
+    {
+        Site site = (Site) event.getTarget();
+        if (site != null)
+        {
+            Request request = ContextHelper.getRequest(_context);
+
+            // Retrieve current workspace
+            String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
+
+            try
+            {
+                // Use live workspace
+                RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
+
+                _updateOdfRootIndex(site, event);
+            }
+            finally
+            {
+                // Restore context
+                RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
+            }
+        }
+    }
+
+    private void _updateOdfRootIndex(Site site, Event event)
+    {
+        for (Sitemap sitemap : site.getSitemaps())
+        {
+            IndexWriter indexWriter = null;
+            try
+            {
+                indexWriter = IndexerHelper.getIndexWriter(_cocoonContext, _pageIndexer, sitemap.getSiteName(), sitemap.getSitemapName());
+                boolean indexModified = false;
+
+                for (Page odfRootPage : _odfPageHandler.getOdfRootPages(sitemap.getSiteName(), sitemap.getSitemapName()))
+                {
+                    Page liveOdfRootPage = _getPageInLive(odfRootPage.getId());
+
+                    if (liveOdfRootPage != null)
+                    {
+                        _updateIndex(indexWriter, liveOdfRootPage);
+
+                        indexModified = true;
+                    }
+                }
+
+                if (indexModified)
+                {
+                    // Invalidate the index term cache.
+                    _indexTermCache.invalidate();
+
+                    // Commit index
+                    indexWriter.commit();
+                }
+            }
+            catch (Exception e)
+            {
+                getLogger().error("Unable to invalidate ODF cache for Site " + site, e);
+            }
+            finally
+            {
+                if (indexWriter != null)
+                {
+                    try
+                    {
+                        indexWriter.close();
+                    }
+                    catch (Exception e)
+                    {
+                        getLogger().error("Unable to close index for event: " + event, e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the index from the event observed. 
+     * @param indexWriter the index writer.
+     * @param liveOdfRootPage the live odf root page
+     * @throws Exception if an error occurs.
+     */
+    protected abstract void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception;
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOrgUnitMovedObserver.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOrgUnitMovedObserver.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/AbstractLuceneOrgUnitMovedObserver.java	(revision 0)
@@ -0,0 +1,190 @@
+/*
+ *  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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.cms.repository.RequestAttributeWorkspaceSelector;
+import org.ametys.odf.observation.OdfObservationConstants;
+import org.ametys.odf.orgunit.OrgUnit;
+import org.ametys.plugins.odfweb.repository.OdfPageHandler;
+import org.ametys.web.WebConstants;
+import org.ametys.web.lucene.AbstractLuceneObserver;
+import org.ametys.web.lucene.IndexTermCache;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.site.SiteManager;
+import org.ametys.web.repository.sitemap.Sitemap;
+import org.ametys.web.site.SiteConfigurationExtensionPoint;
+
+/**
+ * {@link Observer} for observing orgunit move modifications
+ * in order to synchronize lucene index associated.
+ */
+public abstract class AbstractLuceneOrgUnitMovedObserver extends AbstractLuceneObserver
+{
+    private SiteManager _siteManager;
+    private OdfPageHandler _odfPageHandler;
+    private SiteConfigurationExtensionPoint _siteConf;
+    private IndexTermCache _indexTermCache;
+
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);
+        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
+        _odfPageHandler = (OdfPageHandler) serviceManager.lookup(OdfPageHandler.ROLE);
+        _siteConf = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
+        _indexTermCache = (IndexTermCache) serviceManager.lookup(IndexTermCache.ROLE);
+    }
+
+    @Override
+    public boolean supports(Event event)
+    {
+        return event.getId().equals(OdfObservationConstants.ODF_ORGUNIT_MOVED);
+    }
+
+    public void observe(Event event)
+    {
+        OrgUnit oldOrgUnit = (OrgUnit) event.getArguments()[0];
+        OrgUnit newOrgUnit = (OrgUnit) event.getArguments()[1];
+
+        Request request = ContextHelper.getRequest(_context);
+
+        // Retrieve current workspace
+        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
+
+        try
+        {
+            // Use live workspace
+            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
+
+            for (Site site : _siteManager.getSites())
+            {
+                if (_odfPageHandler.hasOdfRootPage(site))
+                {
+                    String orgUnitId = _siteConf.getValueAsString(site.getName(), "display-orgunit-only");
+
+                    boolean movedFromODF = _isEqualOrIsParentOrgUnit(oldOrgUnit, orgUnitId);
+                    boolean movedToODF = _isEqualOrIsParentOrgUnit(newOrgUnit, orgUnitId);
+
+                    if (StringUtils.isNotEmpty(orgUnitId))
+                    {
+                        // Test if the OrgUnit in the event was moved in or out of the ODF Virtual Tree, and the index need to be updated
+                        // Don't update if the orgunit is still in the ODF Tree ou still out of the ODF Tree.
+                        if (movedFromODF ^ movedToODF)
+                        {
+                            _regenerateOdfIndex(site, event);
+                        }
+                    }
+                }
+            }
+        }
+        finally
+        {
+            // Restore context
+            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
+        }
+    }
+
+    private void _regenerateOdfIndex(Site site, Event event)
+    {
+        for (Sitemap sitemap : site.getSitemaps())
+        {
+            IndexWriter indexWriter = null;
+            try
+            {
+                indexWriter = IndexerHelper.getIndexWriter(_cocoonContext, _pageIndexer, sitemap.getSiteName(), sitemap.getSitemapName());
+                boolean indexModified = false;
+
+                for (Page odfRootPage : _odfPageHandler.getOdfRootPages(sitemap.getSiteName(), sitemap.getSitemapName()))
+                {
+                    Page liveOdfRootPage = _getPageInLive(odfRootPage.getId());
+
+                    if (liveOdfRootPage != null)
+                    {
+                        _updateIndex(indexWriter, liveOdfRootPage);
+                        
+                        indexModified = true;
+                    }
+                }
+
+                if (indexModified)
+                {
+                    // Invalidate the index term cache.
+                    _indexTermCache.invalidate();
+
+                    // Commit index
+                    indexWriter.commit();
+                }
+            }
+            catch (Exception e)
+            {
+                getLogger().error("Unable to invalidate ODF cache for Site " + site, e);
+            }
+            finally
+            {
+                if (indexWriter != null)
+                {
+                    try
+                    {
+                        indexWriter.close();
+                    }
+                    catch (Exception e)
+                    {
+                        getLogger().error("Unable to close index for event: " + event, e);
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean _isEqualOrIsParentOrgUnit(OrgUnit child, String parent)
+    {
+        if (parent.equals(child.getId()))
+        {
+            return true;
+        }
+
+        // Check parents
+        OrgUnit ou = child.getParentOrgUnit();
+        while (ou != null)
+        {
+            if (ou.getId().equals(parent))
+            {
+                return true;
+            }
+            ou = ou.getParentOrgUnit();
+        }
+        return false;
+    }
+
+    /**
+     * Update the index from the event observed. 
+     * @param indexWriter the index writer.
+     * @param liveOdfRootPage the live odf root page
+     * @throws Exception if an error occurs.
+     */
+    protected abstract void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception;
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/ClearODFPageCacheObserver.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/ClearODFPageCacheObserver.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/ClearODFPageCacheObserver.java	(working copy)
@@ -19,41 +19,63 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.environment.Request;
 
 import org.ametys.cms.observation.Event;
 import org.ametys.cms.observation.Observer;
+import org.ametys.cms.repository.RequestAttributeWorkspaceSelector;
+import org.ametys.odf.observation.OdfObservationConstants;
+import org.ametys.odf.orgunit.OrgUnit;
 import org.ametys.plugins.odfweb.repository.ODFPageCache;
 import org.ametys.plugins.odfweb.repository.OdfPageHandler;
 import org.ametys.plugins.odfweb.repository.VirtualPageFactory;
 import org.ametys.plugins.repository.AmetysObjectResolver;
 import org.ametys.plugins.repository.jcr.JCRAmetysObject;
+import org.ametys.web.WebConstants;
 import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.site.SiteManager;
+import org.ametys.web.repository.sitemap.Sitemap;
 
 /**
  * Clear ODF caches when page ODF root page has changed
  *
  */
-public class ClearODFPageCacheObserver extends AbstractLogEnabled implements Observer, Serviceable
+public class ClearODFPageCacheObserver extends AbstractLogEnabled implements Observer, Serviceable, Contextualizable
 {
     /** The ODF root page handler. */
     protected OdfPageHandler _odfPageHandler;
     /** The ODF page cache */
     protected ODFPageCache _odfPageCache;
-    
+    private SiteManager _siteManager;
+    private Context _context;
+
     @Override
     public void service(ServiceManager manager) throws ServiceException
     {
         _odfPageHandler = (OdfPageHandler) manager.lookup(OdfPageHandler.ROLE);
         _odfPageCache = (ODFPageCache) manager.lookup(ODFPageCache.ROLE);
+        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
     }
     
+    @Override
+    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
+    {
+        _context = context;
+    }
+
     public boolean supports(Event event)
     {
-        return event.getId().equals(org.ametys.web.ObservationConstants.PAGE_CHANGED);
+        return event.getId().equals(org.ametys.web.ObservationConstants.PAGE_CHANGED) || 
+                event.getId().equals(OdfObservationConstants.ODF_ORGUNIT_MOVED);
     }
 
     public int getPriority(Event event)
@@ -61,22 +83,55 @@
         // Will be processed AFTER live synchronization observers and BEFORE page indexation observer
         return MAX_PRIORITY + 1100;
     }
-    
+
     public void observe(Event event)
     {
         if (event.getTarget() instanceof Page)
         {
             Page page = (Page) event.getTarget();
-            
+
             if (_isOdfRootPage(page))
             {
                 // Clear ODF page cache
                 _odfPageCache.clearCache(page);
             }
         }
-        
+
+        if (event.getTarget() instanceof OrgUnit)
+        {
+
+            Request request = ContextHelper.getRequest(_context);
+
+            // Retrieve current workspace
+            String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
+
+            try
+            {
+                // Use live workspace
+                RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
+                for (Site site : _siteManager.getSites())
+                {
+                    if (_odfPageHandler.hasOdfRootPage(site))
+                    {
+                        for (Sitemap sitemap : site.getSitemaps())
+                        {
+                            for (Page page : _odfPageHandler.getOdfRootPages(sitemap.getSiteName(), sitemap.getSitemapName()))
+                            {
+                                // Clear ODF page cache
+                                _odfPageCache.clearCache(page);
+                            }
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                // Restore context
+                RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
+            }
+        }
     }
-    
+
     private boolean _isOdfRootPage (Page page)
     {
         if (page instanceof JCRAmetysObject)
@@ -85,13 +140,13 @@
             {
                 JCRAmetysObject jcrPage = (JCRAmetysObject) page;
                 Node node = jcrPage.getNode();
-                
+
                 Value[] values = new Value[]{};
                 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
                 {
                     values = node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues();
                 }
-                
+
                 for (int i = 0; i < values.length; i++)
                 {
                     if (values[i].getString().equals(VirtualPageFactory.class.getName()))
@@ -105,11 +160,11 @@
                 getLogger().error("Unable to detemine if page '" + page.getId() + "' is a ODF root page", e);
                 return false;
             }
-            
+
         }
-        
+
         return false;
-        
+
     }
-    
+
 }
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/InvalidateCacheOnOrgunitMovedObserver.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/InvalidateCacheOnOrgunitMovedObserver.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/InvalidateCacheOnOrgunitMovedObserver.java	(revision 0)
@@ -0,0 +1,100 @@
+/*
+ *  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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.odf.observation.OdfObservationConstants;
+import org.ametys.odf.orgunit.OrgUnit;
+import org.ametys.plugins.odfweb.repository.OdfPageHandler;
+import org.ametys.web.cache.CacheInvalidationPolicy;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.site.SiteManager;
+
+/**
+ * {@link Observer} for observing OrgUnit validation, depublication or deletion in order to 
+ * invalidate cache on front-office.
+ */
+public class InvalidateCacheOnOrgunitMovedObserver extends AbstractLogEnabled implements Observer, Serviceable
+{
+    
+    /** The site manager. */
+    protected SiteManager _siteManager;
+    
+    /** The ODF page handler. */
+    protected OdfPageHandler _odfPageHandler;
+    
+    /** Cache invalidation policy */
+    protected CacheInvalidationPolicy _cachePolicy;
+    
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
+        _odfPageHandler = (OdfPageHandler) serviceManager.lookup(OdfPageHandler.ROLE);
+        _cachePolicy = (CacheInvalidationPolicy) serviceManager.lookup(CacheInvalidationPolicy.ROLE);
+    }
+    
+    @Override
+    public boolean supports(Event event)
+    {
+        return event.getTarget() instanceof OrgUnit && event.getId().equals(OdfObservationConstants.ODF_ORGUNIT_MOVED);
+    }
+    
+    @Override
+    public int getPriority(Event event)
+    {
+        // Will be processed after lucene update
+        return MAX_PRIORITY + 4000;
+    }
+    
+    @Override
+    public void observe(Event event)
+    {
+        OrgUnit oldOrgUnit = (OrgUnit) event.getArguments()[0];
+        OrgUnit newOrgUnit = (OrgUnit) event.getArguments()[1];
+        
+        for (Site site : _siteManager.getSites())
+        {
+            if (_odfPageHandler.hasOdfRootPage(site))
+            {
+                try
+                {
+                    _cachePolicy.invalidateCacheOnContentModification(site, oldOrgUnit);
+                }
+                catch (Exception e)
+                {
+                    getLogger().error("Unable to invalidate cache for OrgUnit " + oldOrgUnit, e);
+                }
+                
+                try
+                {
+                    _cachePolicy.invalidateCacheOnContentModification(site, newOrgUnit);
+                }
+                catch (Exception e)
+                {
+                    getLogger().error("Unable to invalidate cache for OrgUnit " + newOrgUnit, e);
+                }
+            }
+        }
+    }
+    
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneContentValidatedObserver.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneContentValidatedObserver.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneContentValidatedObserver.java	(working copy)
@@ -104,26 +104,29 @@
                     liveAbstractProgramPage = _odfPageResolver.getSubProgramPage(rootOdfPage, (SubProgram) liveAbstractProgram);
                 }
                 
-                if (getLogger().isInfoEnabled())
-                {
-                    getLogger().info("Updating lucene document with id: " + liveAbstractProgramPage.getId());
-                }
-                
-                // Index recursively
-                IndexerHelper.indexPage(liveAbstractProgramPage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
-                
-                if (course != null)
+                if (liveAbstractProgramPage != null)
                 {
-                    // Retrieve course in the live workspace
-                    Page liveCoursePage = _odfPageResolver.getCoursePage(rootOdfPage, course, liveAbstractProgram.getId());
-                    if (liveCoursePage != null)
+                    if (getLogger().isInfoEnabled())
                     {
-                        // Index course page recursively
-                        IndexerHelper.indexPage(liveCoursePage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
+                        getLogger().info("Updating lucene document with id: " + liveAbstractProgramPage.getId());
                     }
-                    else
+                    
+                    // Index recursively
+                    IndexerHelper.indexPage(liveAbstractProgramPage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
+                    
+                    if (course != null)
                     {
-                        getLogger().warn("Can not find live course page for course '" + course.getTitle() + "' (" + course.getId() + ") and parent program '" + liveAbstractProgram.getTitle() + "' (" + liveAbstractProgram.getId() + ")");
+                        // Retrieve course in the live workspace
+                        Page liveCoursePage = _odfPageResolver.getCoursePage(rootOdfPage, course, liveAbstractProgram.getId());
+                        if (liveCoursePage != null)
+                        {
+                            // Index course page recursively
+                            IndexerHelper.indexPage(liveCoursePage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
+                        }
+                        else
+                        {
+                            getLogger().warn("Can not find live course page for course '" + course.getTitle() + "' (" + course.getId() + ") and parent program '" + liveAbstractProgram.getTitle() + "' (" + liveAbstractProgram.getId() + ")");
+                        }
                     }
                 }
             }
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart1Observer.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart1Observer.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart1Observer.java	(revision 0)
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2016 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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+
+/**
+ * {@link Observer} for observing site configuration modifications
+ * in order to synchronize lucene index associated.
+ */
+public class LuceneOnSiteConfModifiedPart1Observer extends AbstractLuceneOnSiteConfModifiedObserver
+{
+    @Override
+    public int getPriority(Event event)
+    {
+        // Unindex while we still have the old site configuration
+        return MAX_PRIORITY + 500;
+    }
+    
+    @Override
+    protected void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception
+    {
+        // Unindex page and sub-pages
+        IndexerHelper.unIndexPage(liveOdfRootPage, indexWriter, getLogger());
+    }
+
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart2Observer.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart2Observer.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOnSiteConfModifiedPart2Observer.java	(revision 0)
@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2016 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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+
+/**
+ * {@link Observer} for observing site configuration modifications
+ * in order to synchronize lucene index associated.
+ */
+public class LuceneOnSiteConfModifiedPart2Observer extends AbstractLuceneOnSiteConfModifiedObserver
+{
+    @Override
+    protected void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception
+    {
+        // Index recursively
+        IndexerHelper.indexPage(liveOdfRootPage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
+    }
+
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart1Observer.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart1Observer.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart1Observer.java	(revision 0)
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2016 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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+
+/**
+ * {@link Observer} for observing orgunit move modifications
+ * in order to synchronize lucene index associated.
+ */
+public class LuceneOrgUnitMovedPart1Observer extends AbstractLuceneOrgUnitMovedObserver
+{
+    @Override
+    public int getPriority(Event event)
+    {
+        // Unindex while we still have the old site configuration
+        return MAX_PRIORITY + 500;
+    }
+    
+    @Override
+    protected void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception
+    {
+        // Unindex page and sub-pages
+        IndexerHelper.unIndexPage(liveOdfRootPage, indexWriter, getLogger());
+    }
+
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart2Observer.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart2Observer.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/observation/LuceneOrgUnitMovedPart2Observer.java	(revision 0)
@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2016 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.
+ */
+package org.ametys.plugins.odfweb.observation;
+
+import org.apache.lucene.index.IndexWriter;
+
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+
+/**
+ * {@link Observer} for observing orgunit move modifications
+ * in order to synchronize lucene index associated.
+ */
+public class LuceneOrgUnitMovedPart2Observer extends AbstractLuceneOrgUnitMovedObserver
+{
+    @Override
+    protected void _updateIndex(IndexWriter indexWriter, Page liveOdfRootPage) throws Exception
+    {
+        // Index recursively
+        IndexerHelper.indexPage(liveOdfRootPage, null, indexWriter, _pageIndexer, _resourceIndexer, true);
+    }
+
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ODFPageCache.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ODFPageCache.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ODFPageCache.java	(working copy)
@@ -18,8 +18,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.avalon.framework.component.Component;
 import org.apache.avalon.framework.context.Context;
@@ -37,8 +40,10 @@
 import org.ametys.cms.repository.ContentTypeExpression;
 import org.ametys.cms.repository.DefaultContent;
 import org.ametys.odf.SynchronizeExpression;
+import org.ametys.odf.SynchronizeMultiValuesExpression;
 import org.ametys.odf.catalog.CatalogsManager;
 import org.ametys.odf.contenttype.ODFContentType;
+import org.ametys.odf.orgunit.OrgUnit;
 import org.ametys.odf.program.Program;
 import org.ametys.odf.program.ProgramFactory;
 import org.ametys.plugins.repository.AmetysObjectIterable;
@@ -49,6 +54,7 @@
 import org.ametys.plugins.repository.query.expression.AndExpression;
 import org.ametys.plugins.repository.query.expression.Expression;
 import org.ametys.plugins.repository.query.expression.Expression.Operator;
+import org.ametys.plugins.repository.query.expression.OrExpression;
 import org.ametys.plugins.repository.query.expression.StringExpression;
 import org.ametys.web.repository.page.Page;
 
@@ -101,24 +107,32 @@
         
         // Map<lang, Map<catalog, Map<level1, Map<level2, Collection<Program>>>>>
         @SuppressWarnings("unchecked")
-        Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>> cache = (Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>>) request.getAttribute(requestAttributeName);
+        Map<String, Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>>> cache = (Map<String, Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>>>) request.getAttribute(requestAttributeName);
         
         if (cache == null)
         {
-            cache = new HashMap<String, Map<String, Map<String, Map<String, Collection<Program>>>>>();
+            cache = new HashMap<String, Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>>>();
             
             request.setAttribute(requestAttributeName, cache);
         }
         
-        Map<String, Map<String, Map<String, Collection<Program>>>> catalogCache = cache.get(lang);
+        Map<String, Map<String, Map<String, Map<String, Collection<Program>>>>> catalogCache = cache.get(lang);
 
         if (catalogCache == null)
         {
-            catalogCache = new HashMap<String, Map<String, Map<String, Collection<Program>>>>();
+            catalogCache = new HashMap<String, Map<String, Map<String, Map<String, Collection<Program>>>>>();
             cache.put(lang, catalogCache);
         }
+        
+        Map<String, Map<String, Map<String, Collection<Program>>>> orgUnitCache = catalogCache.get(catalog);
+        
+        if (orgUnitCache == null)
+        {
+            orgUnitCache = new HashMap<String, Map<String, Map<String, Collection<Program>>>>();
+            catalogCache.put(catalog, orgUnitCache);
+        }
 
-        Map<String, Map<String, Collection<Program>>> sortedLevel1Cache = catalogCache.get(catalog);
+        Map<String, Map<String, Collection<Program>>> sortedLevel1Cache = orgUnitCache.get(orgUnitId);
                 
         if (sortedLevel1Cache == null)
         {
@@ -132,14 +146,14 @@
             
             sortedLevel1Cache = new LinkedHashMap<String, Map<String, Collection<Program>>>(); // ODF-763
             Map<String, Map<String, Collection<Program>>> tmpLevel1Cache = new HashMap<String, Map<String, Collection<Program>>>();
-            catalogCache.put(catalog, sortedLevel1Cache);
+            orgUnitCache.put(orgUnitId, sortedLevel1Cache);
             
             Expression contentExpression = new AndExpression(new ContentTypeExpression(Operator.EQ, "org.ametys.plugins.odf.Content.program"),
                                                              new ContentLanguageExpression(Operator.EQ, lang));
             
             if (StringUtils.isNotEmpty(orgUnitId))
             {
-                contentExpression = new AndExpression(contentExpression, _getSyncExpression("orgUnit", orgUnitId));
+                contentExpression = new AndExpression(contentExpression, _getOrgUnitsSyncExpression("orgUnit", orgUnitId));
             }
             
             if (StringUtils.isNotEmpty(catalog) && !_catalogsManager.getCatalogs().isEmpty())
@@ -226,7 +240,7 @@
         
         if (StringUtils.isNotEmpty(orgUnitId))
         {
-            contentExpression = new AndExpression(contentExpression, _getSyncExpression("orgUnit", orgUnitId));
+            contentExpression = new AndExpression(contentExpression, _getOrgUnitsSyncExpression("orgUnit", orgUnitId));
         }
         
         SortCriteria sortCriteria = new SortCriteria();
@@ -341,4 +355,37 @@
             return new StringExpression(metadataName, Operator.EQ, value);
         }
     }
+
+    private Expression _getOrgUnitsSyncExpression(String metadataName, String orgUnitId)
+    {
+        Set<String> orgUnits = _getAllSubOrgUnits(orgUnitId);
+        
+        if (_cType.isSynchronized(metadataName))
+        {
+            return new SynchronizeMultiValuesExpression(metadataName, orgUnits);
+        }
+        else
+        {
+            List<Expression> exprValues = new ArrayList<Expression>();
+            for (String orgUnit : orgUnits)
+            {
+                exprValues.add(new StringExpression(metadataName, Operator.EQ, orgUnit));
+            }
+            return new OrExpression(exprValues.toArray(new Expression[exprValues.size()]));
+        }
+    }
+    
+    private Set<String> _getAllSubOrgUnits(String orgUnitId)
+    {
+        Set<String> ids = new HashSet<String>();
+        ids.add(orgUnitId);
+        
+        OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
+        for (String subOrgUnitId : orgUnit.getSubOrgUnits())
+        {
+            ids.addAll(_getAllSubOrgUnits(subOrgUnitId));
+        }
+        
+        return ids;
+    }
 }
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ProgramPageFactory.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ProgramPageFactory.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/ProgramPageFactory.java	(working copy)
@@ -16,16 +16,23 @@
 
 package org.ametys.plugins.odfweb.repository;
 
+import java.util.List;
+
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
+import org.apache.commons.lang.StringUtils;
 
 import org.ametys.odf.enumeration.OdfEnumerationI18nCache;
+import org.ametys.odf.orgunit.OrgUnit;
 import org.ametys.odf.program.AbstractProgram;
+import org.ametys.plugins.repository.AmetysObject;
 import org.ametys.plugins.repository.AmetysObjectFactory;
 import org.ametys.plugins.repository.AmetysObjectResolver;
 import org.ametys.plugins.repository.AmetysRepositoryException;
+import org.ametys.plugins.repository.UnknownAmetysObjectException;
 import org.ametys.web.repository.page.Page;
+import org.ametys.web.site.SiteConfigurationExtensionPoint;
 
 /**
  * {@link AmetysObjectFactory} handling {@link ProgramPage}.
@@ -34,12 +41,14 @@
 {
     AmetysObjectResolver _resolver;
     OdfEnumerationI18nCache _i18nCache;
+    private SiteConfigurationExtensionPoint _siteConf;
     
     @Override
     public void service(ServiceManager manager) throws ServiceException
     {
         _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
         _i18nCache = (OdfEnumerationI18nCache) manager.lookup(OdfEnumerationI18nCache.ROLE);
+        _siteConf = (SiteConfigurationExtensionPoint) manager.lookup(SiteConfigurationExtensionPoint.ROLE);
     }
     
     @Override
@@ -57,9 +66,35 @@
         
         AbstractProgram program = _resolver.resolveById(programId);
         
+        String orgUnitId = _siteConf.getValueAsString(root.getSiteName(), "display-orgunit-only");
+        
+        if (StringUtils.isNotEmpty(orgUnitId) && !_isProgramInOrgUnit(program, orgUnitId))
+        {
+            throw new UnknownAmetysObjectException("There's no program child page " + programId + " for site " + root.getSiteName());
+        }
+        
         return new ProgramPage(_resolver, root, program, path, null);
     }
 
+    private boolean _isProgramInOrgUnit(AbstractProgram program, String rootOrgUnit)
+    {
+        List<String> programOrgUnitIds = program.getOrgUnits();
+        for (String orgUnitId : programOrgUnitIds)
+        {
+            OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
+            while (orgUnit != null)
+            {
+                if (orgUnit.getId().equals(rootOrgUnit))
+                {
+                    return true;
+                }
+                orgUnit = orgUnit.getParentOrgUnit();
+            }
+        }
+        
+        return false;
+    }
+
     @Override
     public String getScheme()
     {
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/SecondLevelPage.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/SecondLevelPage.java	(revision 38750)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/repository/SecondLevelPage.java	(working copy)
@@ -18,6 +18,8 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -31,7 +33,9 @@
 import org.ametys.cms.repository.ContentTypeExpression;
 import org.ametys.cms.repository.DefaultContent;
 import org.ametys.odf.SynchronizeExpression;
+import org.ametys.odf.SynchronizeMultiValuesExpression;
 import org.ametys.odf.contenttype.ODFContentType;
+import org.ametys.odf.orgunit.OrgUnit;
 import org.ametys.odf.program.Program;
 import org.ametys.plugins.explorer.resources.ResourceCollection;
 import org.ametys.plugins.repository.AmetysObject;
@@ -47,6 +51,7 @@
 import org.ametys.plugins.repository.query.expression.AndExpression;
 import org.ametys.plugins.repository.query.expression.Expression;
 import org.ametys.plugins.repository.query.expression.Expression.Operator;
+import org.ametys.plugins.repository.query.expression.OrExpression;
 import org.ametys.plugins.repository.query.expression.StringExpression;
 import org.ametys.web.repository.page.Page;
 import org.ametys.web.repository.page.UnknownZoneException;
@@ -342,7 +347,7 @@
         
         if (StringUtils.isNotEmpty(_onlyOrgUnitId))
         {
-            contentExpression = new AndExpression(contentExpression, _getSyncExpression("orgUnit", _onlyOrgUnitId));
+            contentExpression = new AndExpression(contentExpression, _getOrgUnitsSyncExpression("orgUnit", _onlyOrgUnitId));
         }
         
         SortCriteria sortCriteria = new SortCriteria();
@@ -363,6 +368,39 @@
         }
     }
 
+    private Expression _getOrgUnitsSyncExpression(String metadataName, String orgUnitId)
+    {
+        Set<String> orgUnits = _getAllSubOrgUnits(orgUnitId);
+        
+        if (_cType.isSynchronized(metadataName))
+        {
+            return new SynchronizeMultiValuesExpression(metadataName, orgUnits);
+        }
+        else
+        {
+            List<Expression> exprValues = new ArrayList<Expression>();
+            for (String orgUnit : orgUnits)
+            {
+                exprValues.add(new StringExpression(metadataName, Operator.EQ, orgUnit));
+            }
+            return new OrExpression(exprValues.toArray(new Expression[exprValues.size()]));
+        }
+    }
+    
+    private Set<String> _getAllSubOrgUnits(String orgUnitId)
+    {
+        Set<String> ids = new HashSet<String>();
+        ids.add(orgUnitId);
+        
+        OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
+        for (String subOrgUnitId : orgUnit.getSubOrgUnits())
+        {
+            ids.addAll(_getAllSubOrgUnits(subOrgUnitId));
+        }
+        
+        return ids;
+    }
+    
     @Override
     public String getId() throws AmetysRepositoryException
     {
