001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.odf; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Optional; 022import java.util.Set; 023 024import javax.xml.transform.TransformerFactory; 025import javax.xml.transform.dom.DOMResult; 026import javax.xml.transform.sax.SAXTransformerFactory; 027import javax.xml.transform.sax.TransformerHandler; 028 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang3.StringUtils; 034import org.w3c.dom.Node; 035import org.w3c.dom.NodeList; 036 037import org.ametys.cms.repository.Content; 038import org.ametys.cms.repository.ContentTypeExpression; 039import org.ametys.core.util.dom.AmetysNodeList; 040import org.ametys.odf.course.Course; 041import org.ametys.odf.courselist.CourseList; 042import org.ametys.odf.courselist.CourseList.ChoiceType; 043import org.ametys.odf.coursepart.CoursePart; 044import org.ametys.odf.enumeration.OdfReferenceTableEntry; 045import org.ametys.odf.enumeration.OdfReferenceTableHelper; 046import org.ametys.odf.orgunit.OrgUnit; 047import org.ametys.odf.orgunit.OrgUnitFactory; 048import org.ametys.odf.orgunit.RootOrgUnitProvider; 049import org.ametys.odf.program.AbstractProgram; 050import org.ametys.odf.program.Program; 051import org.ametys.odf.program.SubProgram; 052import org.ametys.odf.xslt.OdfReferenceTableElement; 053import org.ametys.odf.xslt.ProgramElement; 054import org.ametys.odf.xslt.SubProgramElement; 055import org.ametys.plugins.repository.AmetysObjectIterable; 056import org.ametys.plugins.repository.AmetysObjectResolver; 057import org.ametys.plugins.repository.AmetysRepositoryException; 058import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater; 059import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry; 060import org.ametys.plugins.repository.query.QueryHelper; 061import org.ametys.plugins.repository.query.expression.AndExpression; 062import org.ametys.plugins.repository.query.expression.Expression; 063import org.ametys.plugins.repository.query.expression.StringExpression; 064import org.ametys.plugins.repository.query.expression.Expression.Operator; 065import org.ametys.runtime.config.Config; 066 067/** 068 * Helper component to be used from XSL stylesheets. 069 */ 070public class OdfXSLTHelper implements Serviceable 071{ 072 /** The ODF helper */ 073 protected static ODFHelper _odfHelper; 074 /** The ODF reference helper */ 075 protected static OdfReferenceTableHelper _odfRefTableHelper; 076 /** The Ametys resolver */ 077 protected static AmetysObjectResolver _ametysObjectResolver; 078 /** The orgunit root provider */ 079 protected static RootOrgUnitProvider _rootOrgUnitProvider; 080 081 @Override 082 public void service(ServiceManager smanager) throws ServiceException 083 { 084 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 085 _odfRefTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE); 086 _ametysObjectResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 087 _rootOrgUnitProvider = (RootOrgUnitProvider) smanager.lookup(RootOrgUnitProvider.ROLE); 088 } 089 090 /** 091 * Get the label associated with the degree key 092 * @param cdmValue The code of degree 093 * @return The label of degree or code if not found 094 */ 095 public static String degreeLabel (String cdmValue) 096 { 097 return degreeLabel(cdmValue, Config.getInstance().getValue("odf.programs.lang")); 098 } 099 100 /** 101 * Get the code associated with the given reference table's entry 102 * @param tableRefEntryId The id of entry 103 * @return the code or <code>null</code> if not found 104 */ 105 public static String getCode (String tableRefEntryId) 106 { 107 try 108 { 109 Content content = _ametysObjectResolver.resolveById(tableRefEntryId); 110 return content.getValue(OdfReferenceTableEntry.CODE); 111 } 112 catch (AmetysRepositoryException e) 113 { 114 return null; 115 } 116 } 117 118 /** 119 * Get the id of reference table's entry 120 * @param tableRefId The id of content type 121 * @param code The code 122 * @return the id or <code>null</code> if not found 123 */ 124 public static String getEntryId (String tableRefId, String code) 125 { 126 OdfReferenceTableEntry entry = _odfRefTableHelper.getItemFromCode(tableRefId, code); 127 if (entry != null) 128 { 129 return entry.getId(); 130 } 131 return null; 132 } 133 134 /** 135 * Get the label associated with the degree key 136 * @param cdmValue The cdm value of degree 137 * @param lang The language 138 * @return The label of degree or empty string if not found 139 */ 140 public static String degreeLabel (String cdmValue, String lang) 141 { 142 return Optional 143 .ofNullable(_odfRefTableHelper.getItemFromCDM(OdfReferenceTableHelper.DEGREE, cdmValue)) 144 .map(degree -> degree.getLabel(lang)) 145 .orElse(StringUtils.EMPTY); 146 } 147 148 /** 149 * Get the whole structure of a subprogram, including the structure of child subprograms 150 * @param subprogramId The id of subprogram 151 * @return Node with the subprogram structure 152 */ 153 public static Node getSubProgramStructure (String subprogramId) 154 { 155 SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId); 156 return new SubProgramElement(subProgram, _ametysObjectResolver); 157 } 158 159 /** 160 * Get the structure of a subprogram, including the structure of child subprograms until the given depth 161 * @param subprogramId The id of subprogram 162 * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms. 163 * @return Node with the subprogram structure 164 */ 165 public static Node getSubProgramStructure (String subprogramId, int depth) 166 { 167 SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId); 168 return new SubProgramElement(subProgram, depth, null, _ametysObjectResolver); 169 } 170 171 /** 172 * Get the parent program information 173 * @param subprogramId The id of subprogram 174 * @return a node for each program's information 175 */ 176 public static NodeList getParentProgram (String subprogramId) 177 { 178 return getParentProgramStructure(subprogramId, 0); 179 } 180 181 /** 182 * Get the certification label of a {@link AbstractProgram}. 183 * Returns null if the program is not certified. 184 * @param abstractProgramId the id of program or subprogram 185 * @return the certification label 186 */ 187 public static String getCertificationLabel(String abstractProgramId) 188 { 189 AbstractProgram abstractProgram = _ametysObjectResolver.resolveById(abstractProgramId); 190 if (abstractProgram.isCertified()) 191 { 192 String degreeId = null; 193 if (abstractProgram instanceof Program) 194 { 195 degreeId = abstractProgram.getDegree(); 196 } 197 else if (abstractProgram instanceof SubProgram) 198 { 199 // Get degree from parent 200 Set<Program> rootPrograms = abstractProgram.getRootPrograms(); 201 if (rootPrograms.size() > 0) 202 { 203 degreeId = rootPrograms.iterator().next().getDegree(); 204 } 205 } 206 207 if (StringUtils.isNotEmpty(degreeId)) 208 { 209 Content degree = _ametysObjectResolver.resolveById(degreeId); 210 return degree.getValue("certificationLabel"); 211 } 212 } 213 214 return null; 215 } 216 217 /** 218 * Get the program information 219 * @param programId The id of program 220 * @return Node with the program's information 221 */ 222 public static Node getProgram (String programId) 223 { 224 return getProgramStructure(programId, 0); 225 } 226 227 /** 228 * Get the structure of a parent programs, including the structure of child subprograms until the given depth. 229 * @param subprogramId The id of subprogram 230 * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms. 231 * @return a node for each program's structure 232 */ 233 public static NodeList getParentProgramStructure (String subprogramId, int depth) 234 { 235 List<ProgramElement> programs = new ArrayList<>(); 236 237 SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId); 238 239 Set<Program> rootPrograms = subProgram.getRootPrograms(); 240 if (rootPrograms.size() > 0) 241 { 242 programs.add(new ProgramElement(rootPrograms.iterator().next(), depth, null, _ametysObjectResolver)); 243 } 244 245 return new AmetysNodeList(programs); 246 } 247 248 /** 249 * Get the structure of a program until the given depth. 250 * @param programId The id of program 251 * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms. 252 * @return Node with the program structure 253 */ 254 public static Node getProgramStructure (String programId, int depth) 255 { 256 Program program = _ametysObjectResolver.resolveById(programId); 257 return new ProgramElement(program, depth, null, _ametysObjectResolver); 258 } 259 260 /** 261 * Get the items of a reference table 262 * @param tableRefId the id of reference table 263 * @param lang the language to use for labels 264 * @return the items 265 */ 266 public static Node getTableRefItems(String tableRefId, String lang) 267 { 268 return getTableRefItems(tableRefId, lang, false); 269 } 270 271 /** 272 * Get the items of a reference table 273 * @param tableRefId the id of reference table 274 * @param lang the language to use for labels 275 * @param ordered true to sort items by 'order' attribute 276 * @return the items 277 */ 278 public static Node getTableRefItems(String tableRefId, String lang, boolean ordered) 279 { 280 return new OdfReferenceTableElement(tableRefId, _odfRefTableHelper, lang, ordered); 281 } 282 283 /** 284 * Get the id of root orgunit 285 * @return The id of root 286 */ 287 public static String getRootOrgUnitId() 288 { 289 return _rootOrgUnitProvider.getRootId(); 290 } 291 292 /** 293 * Get the id of the first orgunit matching the given UAI code 294 * @param uaiCode the UAI code 295 * @return the id of orgunit or null if not found 296 */ 297 public static String getOrgUnitIdByUAICode(String uaiCode) 298 { 299 Expression expr = new AndExpression( 300 new ContentTypeExpression(Operator.EQ, OrgUnitFactory.ORGUNIT_CONTENT_TYPE), 301 new StringExpression(OrgUnit.CODE_UAI, Operator.EQ, uaiCode) 302 ); 303 304 String xPathQuery = QueryHelper.getXPathQuery(null, OrgUnitFactory.ORGUNIT_NODETYPE, expr); 305 AmetysObjectIterable<OrgUnit> orgUnits = _ametysObjectResolver.query(xPathQuery); 306 307 OrgUnit orgUnit = orgUnits.stream() 308 .findFirst() 309 .orElse(null); 310 311 return orgUnit != null ? orgUnit.getId() : null; 312 } 313 314 /** 315 * Get the more recent educational booklet for one subprogram 316 * @param subProgramId the subprogram id 317 * @return the pdf as an ametys node list 318 */ 319 public static AmetysNodeList getEducationalBooklet(String subProgramId) 320 { 321 try 322 { 323 SubProgram subProgram = _ametysObjectResolver.resolveById(subProgramId); 324 if (subProgram.hasValue("educational-booklets")) 325 { 326 ModifiableModelAwareRepeater repeater = subProgram.getRepeater("educational-booklets"); 327 ZonedDateTime dateToCompare = null; 328 ModifiableModelAwareRepeaterEntry entry = null; 329 for (ModifiableModelAwareRepeaterEntry repeaterEntry : repeater.getEntries()) 330 { 331 ZonedDateTime date = repeaterEntry.getValue("date"); 332 if (dateToCompare == null || date.isAfter(dateToCompare)) 333 { 334 dateToCompare = date; 335 entry = repeaterEntry; 336 } 337 } 338 339 if (entry != null) 340 { 341 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 342 TransformerHandler th = saxTransformerFactory.newTransformerHandler(); 343 344 DOMResult result = new DOMResult(); 345 th.setResult(result); 346 347 th.startDocument(); 348 XMLUtils.startElement(th, "value"); 349 if (entry.hasNonEmptyValue("pdf")) 350 { 351 subProgram.dataToSAX(th, "educational-booklets[" + entry.getPosition() + "]/pdf"); 352 } 353 XMLUtils.endElement(th, "value"); 354 th.endDocument(); 355 356 List<Node> values = new ArrayList<>(); 357 358 // #getChildNodes() returns a NodeList that contains the value(s) saxed 359 // we cannot returns directly this NodeList because saxed values should be wrapped into a <value> tag. 360 NodeList childNodes = result.getNode().getFirstChild().getChildNodes(); 361 for (int i = 0; i < childNodes.getLength(); i++) 362 { 363 Node n = childNodes.item(i); 364 values.add(n); 365 } 366 367 return new AmetysNodeList(values); 368 } 369 } 370 } 371 catch (Exception e) 372 { 373 return null; 374 } 375 376 return null; 377 } 378 379 /** 380 * Count the hours accumulation in the {@link ProgramItem} 381 * @param contentId The id of the {@link ProgramItem} 382 * @return The hours accumulation 383 */ 384 public static Double getCumulatedHours(String contentId) 385 { 386 return _odfHelper.getCumulatedHours(_ametysObjectResolver.<ProgramItem>resolveById(contentId)); 387 } 388}