Index: main/plugin-odf-web/i18n/messages_en.xml
===================================================================
--- main/plugin-odf-web/i18n/messages_en.xml	(revision 36748)
+++ main/plugin-odf-web/i18n/messages_en.xml	(working copy)
@@ -98,6 +98,8 @@
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_COURSE_REFERENCING_PROGRAMS_INTRO">Program(s) integrating the course: </message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_CATALOG">Catalog</message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_CATALOG_DESC">The search will be based on the selected catalog</message>
+	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM">Enable search in subprograms</message>
+	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM_DESC">If subprograms search is enabled, keywords search will be enable on subprograms data too</message>
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_FACET">Enable faceted search</message>
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_FACET_DESC">If faceted search is enabled, choice list criteria display the number of results for each value, and will be refined as values are selected.</message>
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_GROUP_ID">Service identifier</message>
Index: main/plugin-odf-web/i18n/messages_fr.xml
===================================================================
--- main/plugin-odf-web/i18n/messages_fr.xml	(revision 36748)
+++ main/plugin-odf-web/i18n/messages_fr.xml	(working copy)
@@ -98,6 +98,8 @@
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_COURSE_REFERENCING_PROGRAMS_INTRO">Formation(s) intégrant cette UE: </message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_CATALOG">Catalogue</message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_CATALOG_DESC">Catalogue des formations recherchées</message>
+	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM">Rechercher dans les parcours</message>
+	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM_DESC">Si la recherche dans les parcours est activé, la recherche de mots clefs se fera également dans les données des parcours</message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_FACET">Recherche à facettes</message>
 	<message key="PLUGINS_ODFWEB_SERVICE_SEARCH_FACET_DESC">Si les facettes sont activées, les critères listes à choix afficheront le nombre de résultats entre parenthèses, et s'affineront au fur et à mesure de la sélection des critères.</message>
     <message key="PLUGINS_ODFWEB_SERVICE_SEARCH_GROUP_ID">Identifiant du service</message>
Index: main/plugin-odf-web/plugin.xml
===================================================================
--- main/plugin-odf-web/plugin.xml	(revision 36748)
+++ main/plugin-odf-web/plugin.xml	(working copy)
@@ -392,6 +392,10 @@
                             <mandatory/>
                         </validation>
                     </parameter>
+                    <parameter name="subprogram-search" type="boolean">
+                        <label i18n="true">PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM</label>
+                        <description i18n="true">PLUGINS_ODFWEB_SERVICE_SEARCH_SUBPROGRAM_DESC</description>
+                    </parameter>
                     <parameter name="facets" type="boolean">
                         <label i18n="true">PLUGINS_ODFWEB_SERVICE_SEARCH_FACET</label>
                         <description i18n="true">PLUGINS_ODFWEB_SERVICE_SEARCH_FACET_DESC</description>
@@ -1041,6 +1045,9 @@
         <components>
             <component role="org.ametys.odf.contenttype.ODFMetadataIndexer"
                        class="org.ametys.plugins.odfweb.WebODFMetadataIndexer"/>
+                       
+            <component role="org.ametys.odf.contenttype.SubProgramMetadataIndexer"
+                       class="org.ametys.plugins.odfweb.WebSubProgramMetadataIndexer"/>
         </components>
     </feature>
 </plugin>
Index: main/plugin-odf-web/sitemap.xmap
===================================================================
--- main/plugin-odf-web/sitemap.xmap	(revision 36748)
+++ main/plugin-odf-web/sitemap.xmap	(working copy)
@@ -111,6 +111,7 @@
 				<map:generate type="odf-search">
 					<map:parameter name="offset" value="{parent-context-attr:offset}"/>
 					<map:parameter name="fields" value="{parent-context-attr:search-fields}"/>
+                    <map:parameter name="subprograms" value="{parent-context-attr:subprogram-search}"/>
                     <map:parameter name="facets" value="{parent-context-attr:facets}"/>
                 	<map:parameter name="catalog" value="{request-param:catalog}"/>
 					<map:parameter name="service-group-id" value="{parent-context-attr:service-group-id}"/>
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/WebSubProgramMetadataIndexer.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/WebSubProgramMetadataIndexer.java	(revision 0)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/WebSubProgramMetadataIndexer.java	(revision 0)
@@ -0,0 +1,61 @@
+/*
+ *  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;
+
+import java.io.IOException;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.lucene.document.Document;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.contenttype.ContentType;
+import org.ametys.cms.contenttype.MetadataDefinition;
+import org.ametys.cms.contenttype.MetadataSetElement;
+import org.ametys.cms.repository.Content;
+import org.ametys.odf.contenttype.SubProgramMetadataIndexer;
+import org.ametys.plugins.repository.metadata.CompositeMetadata;
+import org.ametys.web.renderingcontext.RenderingContext;
+import org.ametys.web.renderingcontext.RenderingContextHandler;
+
+public class WebSubProgramMetadataIndexer extends SubProgramMetadataIndexer
+{
+    /** Component for getting and setting rendering context */
+    protected RenderingContextHandler _renderingContextHandler;
+    
+    @Override
+    public void service(ServiceManager manager) throws ServiceException
+    {
+        super.service(manager);
+        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
+    }
+    
+    @Override
+    public void indexMetadata(MetadataSetElement metadataSet, CompositeMetadata metadata, String prefix, Content content, ContentType cType, Document document,
+            MetadataSetElement element, String name, String fieldName, MetadataDefinition definition) throws SAXException, IOException, Exception
+    {
+        RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
+        try
+        {
+            _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
+            super.indexMetadata(metadataSet, metadata, prefix, content, cType, document, element, name, fieldName, definition);
+        }
+        finally
+        {
+            _renderingContextHandler.setRenderingContext(currentContext);
+        }
+    }
+}
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/AbstractODFSearchGenerator.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/AbstractODFSearchGenerator.java	(revision 36748)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/AbstractODFSearchGenerator.java	(working copy)
@@ -252,7 +252,7 @@
         }
         
         // Title
-        _addTitleQuery(query, params.get("title"), getBoost("title", serviceId));
+        addTitleQuery(query, params.get("title"), getBoost("title", serviceId));
         
         // Catalog
         String catalog = StringUtils.defaultIfEmpty(params.get("catalog"), parameters.getParameter("catalog", ""));
@@ -319,7 +319,7 @@
         
         // Title
         String title = params.containsKey("title") ? params.get("title").get(0) : null;
-        _addTitleQuery(query, title, getBoost("title", serviceId));
+        addTitleQuery(query, title, getBoost("title", serviceId));
         
         // Catalog
         String catalog = params.containsKey("catalog") ? params.get("catalog").get(0) : null;
@@ -714,7 +714,15 @@
      */
     protected abstract String getContentType();
     
-    private void _addTitleQuery(BooleanQuery query, String title, float boost) throws ParseException, IllegalArgumentException
+    /**
+     * Add title to search query.
+     * @param query
+     * @param title
+     * @param boost
+     * @throws ParseException
+     * @throws IllegalArgumentException
+     */
+    protected void addTitleQuery(BooleanQuery query, String title, float boost) throws ParseException, IllegalArgumentException
     {
         if (!StringUtils.isEmpty(title))
         {
Index: main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ODFSearch.java
===================================================================
--- main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ODFSearch.java	(revision 36748)
+++ main/plugin-odf-web/src/org/ametys/plugins/odfweb/generators/ODFSearch.java	(working copy)
@@ -15,13 +15,31 @@
  */
 package org.ametys.plugins.odfweb.generators;
 
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+import org.apache.cocoon.ProcessingException;
 import org.apache.cocoon.xml.AttributesImpl;
 import org.apache.cocoon.xml.XMLUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.queryParser.QueryParser.Operator;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
 import org.xml.sax.SAXException;
-
+import org.ametys.cms.lucene.FieldNames;
+import org.ametys.odf.contenttype.ODFContentIndexer;
+import org.ametys.odf.contenttype.SubProgramMetadataIndexer;
 import org.ametys.odf.program.ProgramFactory;
+import org.ametys.odf.program.SubProgram;
+import org.ametys.web.lucene.LuceneConstants;
 
 
 /**
@@ -29,6 +47,15 @@
  */
 public class ODFSearch extends AbstractODFSearchGenerator
 {
+    protected boolean _subprogramsSearch;
+    
+    @Override
+    public void generate() throws IOException, SAXException, ProcessingException
+    {
+        _subprogramsSearch = parameters.getParameterAsBoolean("subprograms", false);
+        super.generate();
+    }
+    
     @Override
     protected String getContentType()
     {
@@ -36,6 +63,24 @@
     }
 
     @Override
+    protected String[] getFields()
+    {
+        String[] fields = super.getFields();
+        if (_subprogramsSearch)
+        {
+            String[] returnFields = new String[fields.length * 2];
+            int i = 0;
+            for (String field : fields)
+            {
+                returnFields[i++] = field;
+                returnFields[i++] = SubProgramMetadataIndexer.INDEX_PREFIX_SUBPROGRAM + field;
+            }
+            return returnFields;
+        }
+        return fields;
+    }
+    
+    @Override
     protected void saxAdditionalInfosOnPageHit(Document doc) throws SAXException
     {
         Field[] subprograms = doc.getFields("subprograms");
@@ -49,4 +94,29 @@
             XMLUtils.createElement(contentHandler, "subprogram", attrs);
         }
     }
+    
+    @Override
+    protected void addTitleQuery(BooleanQuery query, String title, float boost) throws ParseException, IllegalArgumentException
+    {
+        if (_subprogramsSearch)
+        {
+            if (!StringUtils.isEmpty(title))
+            {
+                Matcher m = _TEXTFIELD_PATTERN.matcher(title);
+                if (!m.matches())
+                {
+                    throw new IllegalArgumentException(title + " does not match the expected regular expression : " + _TEXTFIELD_PATTERN.pattern());
+                }
+                
+                MultiFieldQueryParser parser = new MultiFieldQueryParser(LuceneConstants.LUCENE_VERSION, new String[] {"title", SubProgramMetadataIndexer.INDEX_PREFIX_SUBPROGRAM + "title"}, _analyser);
+                Query titleQuery = parser.parse(title);
+                titleQuery.setBoost(boost);
+                query.add(titleQuery, BooleanClause.Occur.MUST);
+            }
+        }
+        else
+        {
+            super.addTitleQuery(query, title, boost);
+        }
+    }
 }