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.plugins.odfweb.xslt; 017 018import java.util.List; 019 020import org.apache.avalon.framework.context.Context; 021import org.apache.avalon.framework.context.ContextException; 022import org.apache.avalon.framework.context.Contextualizable; 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.cocoon.components.ContextHelper; 026import org.apache.cocoon.environment.Request; 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.lang3.tuple.Pair; 029 030import org.ametys.cms.repository.Content; 031import org.ametys.odf.EducationalPathHelper; 032import org.ametys.odf.ProgramItem; 033import org.ametys.odf.course.Course; 034import org.ametys.odf.data.EducationalPath; 035import org.ametys.odf.program.AbstractProgram; 036import org.ametys.odf.program.Program; 037import org.ametys.odf.program.SubProgram; 038import org.ametys.odf.skill.ODFSkillsHelper; 039import org.ametys.plugins.odfweb.repository.CoursePage; 040import org.ametys.plugins.odfweb.repository.OdfPageHandler; 041import org.ametys.plugins.odfweb.repository.ProgramPage; 042import org.ametys.plugins.repository.AmetysObject; 043import org.ametys.web.WebConstants; 044import org.ametys.web.repository.page.Page; 045import org.ametys.web.repository.sitemap.Sitemap; 046import org.ametys.web.transformation.xslt.AmetysXSLTHelper; 047 048/** 049 * Helper component to be used from XSL stylesheets. 050 */ 051public class OdfXSLTHelper extends org.ametys.odf.OdfXSLTHelper implements Contextualizable 052{ 053 /** The ODF page handler */ 054 protected static OdfPageHandler _odfPageHandler; 055 056 /** The ODF skills helper */ 057 protected static ODFSkillsHelper _odfSkillsHelper; 058 059 /** The avalon context */ 060 protected static Context _context; 061 062 @Override 063 public void service(ServiceManager smanager) throws ServiceException 064 { 065 super.service(smanager); 066 _odfPageHandler = (OdfPageHandler) smanager.lookup(OdfPageHandler.ROLE); 067 _odfSkillsHelper = (ODFSkillsHelper) smanager.lookup(ODFSkillsHelper.ROLE); 068 } 069 070 public void contextualize(Context context) throws ContextException 071 { 072 _context = context; 073 } 074 075 /** 076 * Get the ODF root page, for a specific site, language. 077 * If there is many ODF root pages, the first page of the list is returned. 078 * @param siteName the desired site name. 079 * @param language the sitemap language to search in. 080 * @return the first ODF root page, or null if not found 081 */ 082 public static String odfRootPage(String siteName, String language) 083 { 084 Page odfRootPage = _odfPageHandler.getOdfRootPage(siteName, language); 085 if (odfRootPage != null) 086 { 087 return odfRootPage.getId(); 088 } 089 return null; 090 } 091 092 /** 093 * Get the ODF root page, for a specific site, language and catalog. 094 * @param siteName the desired site name. 095 * @param language the sitemap language to search in. 096 * @param catalog The ODF catalog 097 * @return the ODF root page, or null if not found 098 */ 099 public static String odfRootPage(String siteName, String language, String catalog) 100 { 101 Page odfRootPage = _odfPageHandler.getOdfRootPage(siteName, language, catalog); 102 if (odfRootPage != null) 103 { 104 return odfRootPage.getId(); 105 } 106 return null; 107 } 108 109 /** 110 * Get the PDF url of a program or a subprogram 111 * @param contentId The content id 112 * @param siteName The site name 113 * @return the PDF url or empty string if the content is not a {@link Program} or {@link SubProgram} 114 */ 115 public static String odfPDFUrl (String contentId, String siteName) 116 { 117 StringBuilder sb = new StringBuilder(); 118 119 Content content = _ametysObjectResolver.resolveById(contentId); 120 if (content instanceof AbstractProgram) 121 { 122 sb.append(AmetysXSLTHelper.uriPrefix()) 123 .append("/plugins/odf-web/") 124 .append(siteName) 125 .append("/_content/") 126 .append(content.getName()) 127 .append(".pdf"); 128 } 129 130 return sb.toString(); 131 } 132 133 /** 134 * Get the id of parent program from the current page 135 * @return the id of parent program or null if not found 136 */ 137 public static String parentProgramId() 138 { 139 String pageId = AmetysXSLTHelper.pageId(); 140 141 if (StringUtils.isNotEmpty(pageId)) 142 { 143 Page page = _ametysObjectResolver.resolveById(pageId); 144 145 AmetysObject parent = page.getParent(); 146 while (!(parent instanceof Sitemap)) 147 { 148 if (parent instanceof ProgramPage) 149 { 150 return ((ProgramPage) parent).getProgram().getId(); 151 } 152 153 parent = parent.getParent(); 154 } 155 } 156 157 return null; 158 } 159 160 /** 161 * Get the ECTS of the current course for the current context if present 162 * @return the ECTS or 0 if not found 163 */ 164 public static double getCurrentEcts() 165 { 166 Pair<ProgramItem, List<EducationalPath>> currentEducationalPaths = _getCurrentEducationalPaths(); 167 if (currentEducationalPaths != null) 168 { 169 ProgramItem programItem = currentEducationalPaths.getLeft(); 170 if (programItem instanceof Course course) 171 { 172 return course.getEcts(currentEducationalPaths.getRight()); 173 } 174 } 175 176 return 0; 177 } 178 179 private static Pair<ProgramItem, List<EducationalPath>> _getCurrentEducationalPaths() 180 { 181 Request request = ContextHelper.getRequest(_context); 182 Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE); 183 184 // First try to get current educational paths from course page if present 185 if (page != null) 186 { 187 if (page instanceof CoursePage coursePage) 188 { 189 Course course = coursePage.getContent(); 190 return Pair.of(course, course.getCurrentEducationalPaths()); 191 } 192 else if (page instanceof ProgramPage programPage) 193 { 194 AbstractProgram abstractProgram = programPage.getContent(); 195 return Pair.of(abstractProgram, abstractProgram.getCurrentEducationalPaths()); 196 } 197 } 198 199 // Then try to get current educational paths from course content if present 200 Content content = (Content) request.getAttribute(Content.class.getName()); 201 return _getCurrentEducationalPaths(content); 202 } 203 204 private static Pair<ProgramItem, List<EducationalPath>> _getCurrentEducationalPaths(Content content) 205 { 206 Request request = ContextHelper.getRequest(_context); 207 if (content != null && (content instanceof Course || content instanceof AbstractProgram)) 208 { 209 // First try to get educational paths from content 210 List<EducationalPath> currentEducationalPaths = content instanceof Course course ? course.getCurrentEducationalPaths() : ((AbstractProgram) content).getCurrentEducationalPaths(); 211 if (currentEducationalPaths == null) 212 { 213 // If null try to get educational paths from request attributes 214 @SuppressWarnings("unchecked") 215 List<ProgramItem> pathFromRequest = (List<ProgramItem>) request.getAttribute(EducationalPathHelper.PROGRAM_ITEM_ANCESTOR_PATH_REQUEST_ATTR); 216 if (pathFromRequest != null) 217 { 218 // In request the path may be a partial path 219 currentEducationalPaths = _odfHelper.getEducationPathFromPath(pathFromRequest); 220 221 // If ancestor is present in request attribute, filter paths that contains this ancestor 222 ProgramItem ancestor = (ProgramItem) request.getAttribute(EducationalPathHelper.ROOT_PROGRAM_ITEM_REQUEST_ATTR); 223 if (ancestor != null) 224 { 225 currentEducationalPaths = currentEducationalPaths.stream() 226 .filter(p -> p.getProgramItemIds().contains(ancestor.getId())) 227 .toList(); 228 } 229 } 230 else 231 { 232 // Cannot determine current educational paths from context, returns all available education paths 233 currentEducationalPaths = _odfHelper.getEducationalPaths((ProgramItem) content, true, true); 234 } 235 } 236 237 return Pair.of((ProgramItem) content, currentEducationalPaths); 238 } 239 240 return null; 241 } 242 243 /** 244 * Get the ECTS of the current course for the current context if present 245 * @param defaultValue The default value 246 * @return the ECTS or 0 if not found 247 */ 248 public static double getCurrentEcts(String defaultValue) 249 { 250 double currentEcts = getCurrentEcts(); 251 return currentEcts != 0 ? currentEcts : (StringUtils.isNotEmpty(defaultValue) ? Double.valueOf(defaultValue) : 0); 252 } 253 254 /** 255 * Determines if the values of ECTS is equals for the course's educational paths in the current context 256 * @param courseId The course id 257 * @return true if the values of ECTS is equals in the current context 258 */ 259 public static boolean areECTSEqual(String courseId) 260 { 261 Course course = _ametysObjectResolver.resolveById(courseId); 262 return _areECTSEqual(course); 263 } 264 265 /** 266 * Determines if the values of ECTS is equals for the current course's educational paths in the current context 267 * @return true if the values of ECTS is equals in the current context 268 */ 269 public static boolean areECTSEqual() 270 { 271 Request request = ContextHelper.getRequest(_context); 272 Content content = (Content) request.getAttribute(Content.class.getName()); 273 if (content != null && content instanceof Course course) 274 { 275 return _areECTSEqual(course); 276 } 277 return false; 278 } 279 280 private static boolean _areECTSEqual(Course course) 281 { 282 Pair<ProgramItem, List<EducationalPath>> currentEducationalPaths = _getCurrentEducationalPaths(course); 283 if (currentEducationalPaths != null) 284 { 285 ProgramItem programItem = currentEducationalPaths.getLeft(); 286 List<EducationalPath> paths = currentEducationalPaths.getRight(); 287 return paths != null ? _odfHelper.isSameValueForPaths(programItem, Course.ECTS_BY_PATH, paths) : _odfHelper.isSameValueForAllPaths(programItem, Course.ECTS_BY_PATH); 288 } 289 else 290 { 291 return _odfHelper.isSameValueForAllPaths(course, Course.ECTS_BY_PATH); 292 } 293 } 294 295 /** 296 * <code>true</code> if the program item is part of an program item (program, subprogram or container) that is excluded from skills 297 * @param programItemId the program item id 298 * @param programPageItemId the program item page id. If null or empty, program item is display with no context, consider that skills are available 299 * @return <code>true</code> if the program item has an excluded parent in it path from the page context 300 */ 301 public static boolean areSkillsUnavailable(String programItemId, String programPageItemId) 302 { 303 if (StringUtils.isBlank(programItemId) || StringUtils.isBlank(programPageItemId)) 304 { 305 // program part is displayed outside a page context, assuming that skills should be displayed 306 return false; 307 } 308 309 ProgramItem programItem = _ametysObjectResolver.resolveById(programItemId); 310 if (programItem instanceof Program) 311 { 312 return _odfSkillsHelper.isExcluded(programItem); 313 } 314 315 Page programItemPage = _ametysObjectResolver.resolveById(programPageItemId); 316 317 ProgramPage closestProgramPage = _getClosestProgramPage(programItemPage); 318 AbstractProgram closestProgramOrSubprogram = closestProgramPage.getProgram(); 319 320 ProgramItem parent = _odfHelper.getParentProgramItem(programItem, closestProgramOrSubprogram); 321 while (parent != null && !(parent instanceof Program)) 322 { 323 if (_odfSkillsHelper.isExcluded(parent)) 324 { 325 // If the parent is excluded, the skills are unavailable 326 return true; 327 } 328 329 // If the closest program parent is a subprogram, continue to its program parent 330 if (closestProgramOrSubprogram instanceof SubProgram && closestProgramOrSubprogram.equals(parent)) 331 { 332 closestProgramOrSubprogram = ((ProgramPage) closestProgramPage.getParent()).getProgram(); 333 } 334 parent = _odfHelper.getParentProgramItem(parent, closestProgramOrSubprogram); 335 } 336 337 return parent != null ? _odfSkillsHelper.isExcluded(parent) : false; 338 } 339 340 private static ProgramPage _getClosestProgramPage(Page page) 341 { 342 Page parentPage = page.getParent(); 343 while (!(parentPage instanceof ProgramPage)) 344 { 345 parentPage = parentPage.getParent(); 346 } 347 348 return (ProgramPage) parentPage; 349 } 350}