/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.course;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.olat.admin.SystemAdminMainController;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.configuration.OLATModule;
import org.olat.core.extensions.ExtManager;
import org.olat.core.extensions.Extension;
import org.olat.core.extensions.ExtensionElement;
import org.olat.core.extensions.action.ActionExtension;
import org.olat.core.extensions.helpers.ExtensionElements;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.translator.Translator;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.notifications.SubscriptionContext;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.assessment.AssessmentManager;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.CourseNodeFactory;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.statistic.ExportManager;
import org.olat.properties.Property;
import org.olat.properties.PropertyManager;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import com.anthonyeden.lib.config.Configuration;
/**
* Initial Date: 02.09.2005 <br>
*
* @author Mike Stock
*/
public class CourseModule implements OLATModule {
private static String CONFIG_DEPLOY_COURSE = "DeployCourseExports";
private static String CONFIG_COURSE_EXPORT_FILE = "CourseExportFile";
private static String CONFIG_COURSE_HELP_FILE = "helpCourseFile";
private static boolean courseChatEnabled = false;
// Repository types
public static String ORES_TYPE_COURSE = OresHelper.calculateTypeName(CourseModule.class);
private static OLATResourceable ORESOURCEABLE_TYPE_COURSE = OresHelper.lookupType(CourseModule.class);
public static final String ORES_COURSE_ASSESSMENT = OresHelper.calculateTypeName(AssessmentManager.class);
private static String helpCourseSoftkey = null;
private Map deplyedCoursePaths;
/** used for migration from 6.3 to 6.4 only **/
private static boolean adminLogIsVisibleForMigrationOnly;
/** used for migration from 6.3 to 6.4 only **/
private static boolean userLogIsVisibleForMigrationOnly;
/** used for migration from 6.3 to 6.4 only **/
private static boolean statisticLogIsVisibleForMigrationOnly;
/**
* @see org.olat.core.configuration.OLATModule#init(com.anthonyeden.lib.config.Configuration)
*/
public void init(Configuration configuration) {
// skip all the expensive course demo setup and deployment when we are in junit mode.
if (Settings.isJUnitTest()) return;
Tracing.logInfo("Initializing the OLAT course system", CourseModule.class);
// extract log file visibilities for course author
setLogVisibilitiesForMigrationOnly(configuration);
/*
* extract course chat availability
*/
setCourseChat(configuration);
// Deploy demo courses
Configuration deployCourses = configuration.getChild(CONFIG_DEPLOY_COURSE);
if (deployCourses != null) {
String enabled = deployCourses.getAttribute("enabled");
if (enabled != null && (enabled.equals("true") || enabled.equals("yes"))) {
Tracing.logInfo("Deploying course exports.", CourseModule.class);
List courseExportFileConfigs = deployCourses.getChildren(CONFIG_COURSE_EXPORT_FILE);
for (Iterator iter = courseExportFileConfigs.iterator(); iter.hasNext();) {
Configuration courseExportFile = (Configuration) iter.next();
String access = courseExportFile.getAttribute("access");
int acc = 1;
try {
acc = Integer.parseInt(access);
if (0 < acc && acc < 5) {
deployCourse(courseExportFile.getValue(), acc);
} else {
Tracing.logInfo("Skipping deployment of course::"
+ courseExportFile.getValue() + " ; access attribute must be 1,2,3 or 4 but values is::"
+ access, CourseModule.class);
}
} catch (NumberFormatException e) {
Tracing.logInfo("Skipping deployment of course::"
+ courseExportFile.getValue() + " ; access attribute must be 1,2,3 or 4 but values is::"
+ access, CourseModule.class);
}
}
} else {
Tracing.logInfo("Skipping deployment of course exports. To deploy course exports, please enable in the configuration file.", CourseModule.class);
}
}
// Deploy help courses
String helpCoursePath = configuration.getChildValue(CONFIG_COURSE_HELP_FILE);
if (StringHelper.containsNonWhitespace(helpCoursePath)) {
// deploy help course with BARG settings, otherwhise search engine can not search the help
RepositoryEntry re = deployCourse(helpCoursePath, 4);
if (re == null) {
Tracing.logWarn("Could not deploy configured help course from path::" + helpCoursePath
+ ". Please check your configuration <helpCourseFile> in CourseModule in olat_config.xml. Disabling help course",
CourseModule.class);
}
helpCourseSoftkey = re.getSoftkey();
Tracing.logInfo(
"Help course successfully configured with path::" + helpCoursePath + " and repository soft key::" + helpCourseSoftkey,
CourseModule.class);
} else {
Tracing.logWarn(
"No help course configured. Configuration <helpCourseFile> is missing in CourseModule in olat_config.xml. Disabling help course",
CourseModule.class);
}
// Cleanup, otherwhise this subjects will have problems in normal OLAT
// operation
DBFactory.getInstance(false).intermediateCommit();
// get CourseNodeFactory... will initialize and bossibly throw
// StartupException
CourseNodeFactory.getInstance();
//
ExtManager extManager = ExtManager.getInstance();
List extensions = extManager.getExtensions();
extensions.add(new AdminCourseExtension());
extManager.setExtensions(extensions);
}
private RepositoryEntry deployCourse(String absOrRelPath, int access) {
String resolvedPath = absOrRelPath;
if (resolvedPath == null) return null;
// let's see if we previousely deployed this path...
RepositoryEntry re = (RepositoryEntry) getDeployedCourses().get(absOrRelPath);
if (re != null) {
Tracing.logInfo("Course at path " + absOrRelPath + " has been previousely deployed. Skipping.", CourseModule.class);
return re;
}
if (resolvedPath.length() > 0 && resolvedPath.charAt(0) != '/') resolvedPath = WebappHelper.getContextRoot() + "/" + resolvedPath;
File exportedCourseZIPFile = new File(resolvedPath);
if (!exportedCourseZIPFile.exists()) {
//do not throw exception as users may upload bad file
Tracing.logWarn("Cannot deploy course from file: " + exportedCourseZIPFile.getAbsolutePath(), CourseModule.class);
return null;
}
re = CourseFactory.deployCourseFromZIP(exportedCourseZIPFile, access);
if (re != null) markAsDeployed(absOrRelPath, re);
return re;
}
/**
* Mark a course as deployed. Remember the key of the repository entry it was
* deployed.
*
* @param courseExportPath
* @param re
*/
private void markAsDeployed(String courseExportPath, RepositoryEntry re) {
PropertyManager pm = PropertyManager.getInstance();
Property prop = pm.createPropertyInstance(null, null, null, "_o3_", "deployedCourses", null, re.getKey(), courseExportPath, null);
pm.saveProperty(prop);
deplyedCoursePaths.put(courseExportPath, re);
}
/**
* Get the Map of deployed courses. Map contains repo entries by path keys.
*
* @return
*/
private Map getDeployedCourses() {
if (deplyedCoursePaths != null) return deplyedCoursePaths;
PropertyManager pm = PropertyManager.getInstance();
List props = pm.findProperties(null, null, null, "_o3_", "deployedCourses");
deplyedCoursePaths = new HashMap(props.size());
for (Iterator iter = props.iterator(); iter.hasNext();) {
Property prop = (Property) iter.next();
Long repoKey = prop.getLongValue();
RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(repoKey);
if (re != null) deplyedCoursePaths.put(prop.getStringValue(), re);
}
return deplyedCoursePaths;
}
/**
* reads the config and overrides the defaults
*
* @param configuration
*/
private void setLogVisibilitiesForMigrationOnly(Configuration configuration) {
String adminLogVisible = "INVISIBLE";
String userLogVisible = "INVISIBLE";
String statisticLogVisible = "INVISIBLE";
Configuration logVisibilityConfig = configuration.getChild("LogVisibilityForCourseAuthor");
if (logVisibilityConfig != null) {
adminLogVisible = logVisibilityConfig.getChildValue("AdminLog");
userLogVisible = logVisibilityConfig.getChildValue("UserLog");
statisticLogVisible = logVisibilityConfig.getChildValue("StatisticLog");
}
adminLogIsVisibleForMigrationOnly = adminLogVisible.equals("VISIBLE");
userLogIsVisibleForMigrationOnly = userLogVisible.equals("VISIBLE");
statisticLogIsVisibleForMigrationOnly = statisticLogVisible.equals("VISIBLE");
}
/**
* @return true if the course author can see/download/modify the admin log
*/
public static boolean isAdminLogVisibleForMigrationOnly() {
return adminLogIsVisibleForMigrationOnly;
}
/**
* @return true if the course author can see/download/modify the user log
*/
public static boolean isUserLogVisibleForMigrationOnly() {
return userLogIsVisibleForMigrationOnly;
}
/**
* @return true if the course author can see/download/modify the statistic log
*/
public static boolean isStatisticLogVisibleForMigrationOnly() {
return statisticLogIsVisibleForMigrationOnly;
}
private void setCourseChat(Configuration configuration){
Configuration courseChatConfig = configuration.getChild("enableCourseChat");
courseChatEnabled = false;//default value false
if(courseChatConfig != null){
courseChatEnabled = courseChatConfig.getValue().equals("true");
}
}
/**
* @see org.olat.core.configuration.OLATModule#destroy()
*/
public void destroy() {
// any caches are destroyed by the coordinator, we do not need to take care here.
}
/**
* Returns the filename of the zipped help course
*
* @return The filename of the zipped help course
*/
public static String getHelpCourseSoftKey() {
return helpCourseSoftkey;
}
/**
* @return type name
*/
public static String getCourseTypeName() {
return ORES_TYPE_COURSE;
}
/**
* @param ce
* @param cn
* @return the generated SubscriptionContext
*/
public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn) {
SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
return sc;
}
/**
* @param ce
* @param cn
* @return a subscriptioncontext with no translations for the user, but only
* to be able to cleanup/obtain
*/
public static SubscriptionContext createTechnicalSubscriptionContext(CourseEnvironment ce, CourseNode cn) {
SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
return sc;
}
/**
* Creates subscription context which points to an element e.g. that is a sub
* element of a node (subsubId). E.g. inside the course node dialog elements
* where a course node can have several forums.
*
* @param ce
* @param cn
* @param subsubId
* @return
*/
public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn, String subsubId) {
SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent() + ":" + subsubId);
return sc;
}
/**
* whether course chat is enabld or not - depends on Instant Messaging enabled! you should check first for
* IM Enabled @see {@link org.olat.instantMessaging.InstantMessagingModule}
* @return
*/
public static boolean isCourseChatEnabled(){
return courseChatEnabled;
}
public static void registerForCourseType(GenericEventListener gel, Identity identity) {
CoordinatorManager.getCoordinator().getEventBus().registerFor(gel, identity, ORESOURCEABLE_TYPE_COURSE);
}
public static void deregisterForCourseType(GenericEventListener gel) {
CoordinatorManager.getCoordinator().getEventBus().deregisterFor(gel, ORESOURCEABLE_TYPE_COURSE);
}
/**
* max number of course nodes
* @return
*/
public static int getCourseNodeLimit() {
return 499;
}
/**
* Extension bypassing the xml configuration way.
* Description:<br>
* TODO: patrickb Class Description for AdminCourseExtension
*
* <P>
* Initial Date: 18.08.2008 <br>
* @author patrickb
*/
class AdminCourseExtension implements Extension {
private ExtensionElements elements = new ExtensionElements();
AdminCourseExtension(){
elements.putExtensionElement(SystemAdminMainController.class.getName(), new ActionExtension() {
public String getDescription(Locale loc) {
Translator transl = Util.createPackageTranslator(CourseModule.class, loc);
return transl.translate("course.admin.infoloadedcourses");
}
public String getActionText(Locale loc) {
Translator transl = Util.createPackageTranslator(CourseModule.class, loc);
return transl.translate("course.admin.infoloadedcourses.link");
}
public Controller createController(UserRequest ureq, WindowControl control, Object arg) {
return new CourseAdminController(ureq, control);
}
});
}
public ExtensionElement getExtensionFor(String extensionPoint) {
return elements.getExtensionElement(extensionPoint);
}
}
}