/**
* 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.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.course.nodes.projectbroker;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.olat.NewControllerFactory;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.SimpleStackedPanel;
import org.olat.core.gui.components.panel.StackedPanel;
import org.olat.core.gui.components.table.BooleanColumnDescriptor;
import org.olat.core.gui.components.table.ColumnDescriptor;
import org.olat.core.gui.components.table.CustomRenderColumnDescriptor;
import org.olat.core.gui.components.table.DefaultColumnDescriptor;
import org.olat.core.gui.components.table.TableController;
import org.olat.core.gui.components.table.TableEvent;
import org.olat.core.gui.components.table.TableGuiConfiguration;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.render.Renderer;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.UserConstants;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.projectbroker.datamodel.CustomField;
import org.olat.course.nodes.projectbroker.datamodel.Project;
import org.olat.course.nodes.projectbroker.datamodel.ProjectBroker;
import org.olat.course.nodes.projectbroker.service.ProjectBrokerMailer;
import org.olat.course.nodes.projectbroker.service.ProjectBrokerManager;
import org.olat.course.nodes.projectbroker.service.ProjectBrokerModuleConfiguration;
import org.olat.course.nodes.projectbroker.service.ProjectGroupManager;
import org.olat.course.properties.CoursePropertyManager;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.BusinessGroup;
/**
*
* @author guretzki
*
*/
public class ProjectListController extends BasicController implements GenericEventListener {
private static final String OPEN_IDENTITY_CMD = "openID";
// List commands
private static final String TABLE_ACTION_SHOW_DETAIL = "cmd.show.detail";
private static final String TABLE_ACTION_ACCOUNT_MANAGER = "cmd.account.manager";
private static final String TABLE_ACTION_SELECT = "cmd.select";
private static final String TABLE_ACTION_CANCEL_SELECT = "cmd.cancel.select";
private VelocityContainer contentVC;
private StackedPanel mainPanel;
private ProjectListTableModel projectListTableModel;
private TableController tableController;
private Controller projectController;
private Link createNewProjectButton;
private Long courseId;
private CourseNode courseNode;
private UserCourseEnvironment userCourseEnv;
private ProjectBrokerModuleConfiguration moduleConfig;
private Long projectBrokerId;
private int numberOfCustomFieldInTable = 0;
private int numberOfEventInTable = 0;
private int nbrSelectedProjects;
private boolean isParticipantInAnyProject;
private CloseableCalloutWindowController calloutCtrl;
private Project currentProject;
private DialogBoxController noDeselectWarning;
private final ProjectBrokerMailer projectBrokerMailer;
private final ProjectGroupManager projectGroupManager;
private final ProjectBrokerManager projectBrokerManager;
/**
* @param ureq
* @param wControl
* @param userCourseEnv
* @param ne
* @param previewMode
*/
protected ProjectListController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, CourseNode courseNode, boolean previewMode) {
super(ureq, wControl);
this.userCourseEnv = userCourseEnv;
this.courseNode = courseNode;
projectBrokerMailer = CoreSpringFactory.getImpl(ProjectBrokerMailer.class);
projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class);
projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class);
courseId = userCourseEnv.getCourseEnvironment().getCourseResourceableId();
moduleConfig = new ProjectBrokerModuleConfiguration(courseNode.getModuleConfiguration());
contentVC = createVelocityContainer("project_list");
// set header info with project-broker run mode [accept.automatically.limited , accept.manually.limited etc.]
String infoProjectBrokerRunMode = "";
if (moduleConfig.isAcceptSelectionManually() && moduleConfig.isAutoSignOut()) {
infoProjectBrokerRunMode = translate("info.projectbroker.runmode.accept.manually.auto.sign.out", Integer.toString(moduleConfig.getNbrParticipantsPerTopic()) );
} else if (moduleConfig.isAcceptSelectionManually()) {
if (moduleConfig.getNbrParticipantsPerTopic() == ProjectBrokerModuleConfiguration.NBR_PARTICIPANTS_UNLIMITED) {
infoProjectBrokerRunMode = translate("info.projectbroker.runmode.accept.manually.unlimited" );
} else {
infoProjectBrokerRunMode = translate("info.projectbroker.runmode.accept.manually.limited", Integer.toString(moduleConfig.getNbrParticipantsPerTopic()) );
}
} else {
if (moduleConfig.getNbrParticipantsPerTopic() == ProjectBrokerModuleConfiguration.NBR_PARTICIPANTS_UNLIMITED) {
infoProjectBrokerRunMode = translate("info.projectbroker.runmode.accept.automatically.unlimited" );
} else {
infoProjectBrokerRunMode = translate("info.projectbroker.runmode.accept.automatically.limited", Integer.toString(moduleConfig.getNbrParticipantsPerTopic()) );
}
}
contentVC.contextPut("infoProjectBrokerRunMode", infoProjectBrokerRunMode);
mainPanel = new SimpleStackedPanel("projectlist_panel");
CoursePropertyManager cpm = userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
if ( (projectGroupManager.isAccountManager(ureq.getIdentity(), cpm, courseNode ) && !previewMode)
|| userCourseEnv.getCourseEnvironment().getCourseGroupManager().isIdentityCourseAdministrator(ureq.getIdentity())
|| ureq.getUserSession().getRoles().isOLATAdmin()) {
contentVC.contextPut("isAccountManager", true);
createNewProjectButton = LinkFactory.createButtonSmall("create.new.project.button", contentVC, this);
createNewProjectButton.setIconLeftCSS("o_icon o_icon_add");
} else {
contentVC.contextPut("isAccountManager", false);
}
// push title and learning objectives, only visible on intro page
contentVC.contextPut("menuTitle", courseNode.getShortTitle());
contentVC.contextPut("displayTitle", courseNode.getLongTitle());
projectBrokerId = projectBrokerManager.getProjectBrokerId(cpm, courseNode);
if (projectBrokerId == null) {
// no project-broker exist => create a new one, happens only once
ProjectBroker projectBroker = projectBrokerManager.createAndSaveProjectBroker();
projectBrokerId = projectBroker.getKey();
projectBrokerManager.saveProjectBrokerId(projectBrokerId, cpm, courseNode);
getLogger().info("no project-broker exist => create a new one projectBrokerId=" + projectBrokerId);
}
tableController = createTableController(ureq, wControl);
OLATResourceable projectBroker = projectBrokerManager.getProjectBroker(projectBrokerId);
CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), projectBroker);
updateProjectListModelOf(tableController, ureq.getIdentity());
contentVC.put("projectList", tableController.getInitialComponent());
mainPanel.setContent(contentVC);
// jump to either the forum or the folder if the business-launch-path says so.
BusinessControl bc = getWindowControl().getBusinessControl();
ContextEntry ce = bc.popLauncherContextEntry();
if ( ce != null) { // a context path is left for me
if (isLogDebugEnabled()) logDebug("businesscontrol (for further jumps) would be: ", bc.toString());
OLATResourceable ores = ce.getOLATResourceable();
if (isLogDebugEnabled()) logDebug("OLATResourceable= " , ores.toString());
Long resId = ores.getResourceableId();
if (resId.longValue() != 0) {
if (isLogDebugEnabled()) logDebug("projectId=" , ores.getResourceableId().toString());
Project proj = projectBrokerManager.getProject(ores.getResourceableId());
if (proj != null) {
activateProjectController(proj, ureq);
} else {
// message not found, do nothing. Load normal start screen
logDebug("Invalid projectId=" , ores.getResourceableId().toString());
}
} else {
//FIXME:chg: Should not happen, occurs when course-node are called
if (isLogDebugEnabled()) logDebug("Invalid projectId=" , ores.getResourceableId().toString());
}
}
putInitialPanel(mainPanel);
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
*/
public void event(UserRequest ureq, Component source, Event event) {
if (source == createNewProjectButton) {
String projectTitle = translate("new.project.title");
int i = 1;
while (projectBrokerManager.existProjectName(projectBrokerId, projectTitle)) {
projectTitle = translate("new.project.title") + i++;
}
String projectGroupName = translate("project.member.groupname", projectTitle);
String projectGroupDescription = translate("project.member.groupdescription", projectTitle);
BusinessGroup projectGroup = projectGroupManager.createProjectGroupFor(projectBrokerId,ureq.getIdentity(), projectGroupName, projectGroupDescription, courseId);
Project project = projectBrokerManager.createAndSaveProjectFor(projectTitle, projectTitle, projectBrokerId, projectGroup);
projectGroupManager.sendGroupChangeEvent(project, courseId, ureq.getIdentity());
getLogger().debug("Created a new project=" + project);
projectController = new ProjectController(ureq, this.getWindowControl(), userCourseEnv, courseNode, project, true, moduleConfig);
listenTo(projectController);
mainPanel.pushContent(projectController.getInitialComponent());
} else if (event.getCommand().equals(OPEN_IDENTITY_CMD)){
Link link = (Link) source;
if (calloutCtrl!=null) {
calloutCtrl.deactivate();
removeAsListenerAndDispose(calloutCtrl);
calloutCtrl = null;
}
openUserInPopup(ureq, (Identity) link.getUserObject());
}
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
*/
public void event(UserRequest urequest, Controller source, Event event) {
if ( (source == tableController) && (event instanceof TableEvent) ) {
handleTableEvent(urequest, (TableEvent)event);
} else if ( (source == projectController) && (event == Event.BACK_EVENT) ) {
mainPanel.popContent();
} else if ((source == projectController) && (event instanceof ProjectBrokerEditorEvent)) {
final ProjectBrokerEditorEvent pbEditEvent = (ProjectBrokerEditorEvent) event;
if (pbEditEvent.isCancelEvent()){
getLogger().info("event form cancelled => delete project");
projectBrokerManager.deleteProject(pbEditEvent.getProject(), true, userCourseEnv.getCourseEnvironment(),
courseNode);
mainPanel.popContent();
updateProjectListModelOf(tableController, urequest.getIdentity());
} else if (pbEditEvent.isCreateEvent() || pbEditEvent.isDeletedEvent()){
mainPanel.popContent();
updateProjectListModelOf(tableController, urequest.getIdentity());
}
} else if (source == noDeselectWarning) {
if(DialogBoxUIFactory.isOkEvent(event)){
handleEnrollAction(urequest, currentProject);
}
}
}
private void handleTableEvent(UserRequest urequest, TableEvent te) {
currentProject = (Project)tableController.getTableDataModel().getObject(te.getRowId());
if ( projectBrokerManager.existsProject( currentProject.getKey() ) ) {
handleTableEventForProject(urequest, te, currentProject);
} else {
this.showInfo("info.project.nolonger.exist", currentProject.getTitle());
updateProjectListModelOf(tableController, urequest.getIdentity());
}
}
private void handleTableEventForProject(UserRequest urequest, TableEvent te, Project selectedProject) {
if ( te.getActionId().equals(TABLE_ACTION_SHOW_DETAIL)) {
activateProjectController(selectedProject, urequest);
} else if ( te.getActionId().equals(TABLE_ACTION_ACCOUNT_MANAGER)) {
activateUserController(selectedProject, urequest, te);
} else if ( te.getActionId().equals(TABLE_ACTION_SELECT)) {
if(!projectGroupManager.isDeselectionAllowed(selectedProject)){
List<String> warningButtons = new ArrayList<String>();
warningButtons.add(translate("info.projectbroker.no.deselect.select"));
warningButtons.add(translate("info.projectbroker.no.deselect.cancel"));
String message = translate("info.projectbroker.deselect.confirmation",selectedProject.getTitle())+"<br/><div class=\"o_important\">"+translate("info.projectbroker.no.deselect")+"</div>";
noDeselectWarning = activateGenericDialog(urequest, translate("info.projectbroker.no.deselect.title"), message, warningButtons, noDeselectWarning);
return;
}
handleEnrollAction(urequest, selectedProject);
} else if ( te.getActionId().equals(TABLE_ACTION_CANCEL_SELECT)) {
handleCancelEnrollmentAction(urequest, selectedProject);
} else {
getLogger().warn("Controller-event-handling: Unkown event=" + te);
}
fireEvent(urequest, te);
}
private void handleCancelEnrollmentAction(UserRequest urequest, Project selectedProject) {
getLogger().debug("start cancelProjectEnrollmentOf identity=" + urequest.getIdentity() + " to project=" + selectedProject);
boolean cancelledEnrollmend = projectBrokerManager.cancelProjectEnrollmentOf(urequest.getIdentity(), selectedProject, moduleConfig);
if (cancelledEnrollmend) {
projectBrokerMailer.sendCancelEnrollmentEmailToParticipant(urequest.getIdentity(), selectedProject, this.getTranslator());
if (selectedProject.isMailNotificationEnabled()) {
projectBrokerMailer.sendCancelEnrollmentEmailToManager(urequest.getIdentity(), selectedProject, this.getTranslator());
}
projectGroupManager.sendGroupChangeEvent(selectedProject, courseId, urequest.getIdentity());
} else {
showInfo("info.msg.could.not.cancel.enrollment");
}
updateProjectListModelOf(tableController, urequest.getIdentity());
}
private void handleEnrollAction(UserRequest urequest, Project selectedProject) {
getLogger().debug("start enrollProjectParticipant identity=" + urequest.getIdentity() + " to project=" + selectedProject);
boolean enrolled = projectBrokerManager.enrollProjectParticipant(urequest.getIdentity(), selectedProject, moduleConfig, nbrSelectedProjects, isParticipantInAnyProject);
if (enrolled) {
projectBrokerMailer.sendEnrolledEmailToParticipant(urequest.getIdentity(), selectedProject, this.getTranslator());
if (selectedProject.isMailNotificationEnabled()) {
projectBrokerMailer.sendEnrolledEmailToManager(urequest.getIdentity(), selectedProject, this.getTranslator());
}
projectGroupManager.sendGroupChangeEvent(selectedProject, courseId, urequest.getIdentity());
} else {
showInfo("info.msg.could.not.enroll");
}
updateProjectListModelOf(tableController, urequest.getIdentity());
}
private void updateProjectListModelOf(TableController tableCtrl, Identity identity) {
List<Project> projects = new ArrayList<Project>(projectBrokerManager.getProjectListBy(projectBrokerId));
nbrSelectedProjects = projectBrokerManager.getNbrSelectedProjects(identity, projects);
isParticipantInAnyProject = projectBrokerManager.isParticipantInAnyProject( identity, projects);
projectListTableModel = new ProjectListTableModel(projects, identity, getTranslator(), moduleConfig, numberOfCustomFieldInTable, numberOfEventInTable, nbrSelectedProjects, isParticipantInAnyProject);
tableCtrl.setTableDataModel(projectListTableModel);
}
private void activateUserController(final Project projectAt, UserRequest urequest, TableEvent tableEvent) {
if (projectAt.getProjectLeaders().isEmpty()) {
this.showInfo("show.info.no.project.leader");
} else if (projectAt.getProjectLeaders().size() > 1) {
VelocityContainer identityVC = createVelocityContainer("identityCallout");
List<Identity> allIdents = projectAt.getProjectLeaders();
ArrayList<Link> identLinks = new ArrayList<Link>(allIdents.size());
for (Identity identity : allIdents) {
String last = identity.getUser().getProperty(UserConstants.LASTNAME, getLocale());
String first = identity.getUser().getProperty(UserConstants.FIRSTNAME, getLocale());
String linkName = last + " " + first;
Link idLink = LinkFactory.createCustomLink(linkName, OPEN_IDENTITY_CMD, linkName, Link.NONTRANSLATED, identityVC, this);
idLink.setUserObject(identity);
identLinks.add(idLink);
}
identityVC.contextPut("identLinks", identLinks);
int row = tableEvent.getRowId();
String targetDomID = ProjectManagerColumnRenderer.PROJECTMANAGER_COLUMN_ROW_IDENT + row;
String title = translate("projectlist.callout.title", projectAt.getTitle());
removeAsListenerAndDispose(calloutCtrl);
calloutCtrl = new CloseableCalloutWindowController(urequest, getWindowControl(), identityVC, targetDomID, title, true, null);
calloutCtrl.activate();
listenTo(calloutCtrl);
} else if (projectAt.getProjectLeaders().size() == 1) {
// no callout, if its only one user
Identity leader = projectAt.getProjectLeaders().get(0);
openUserInPopup(urequest, leader);
}
}
private void openUserInPopup(UserRequest ureq, final Identity ident){
String businessPath = "[HomePage:" + ident.getKey() + "]";
NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
}
private void activateProjectController(Project project, UserRequest urequest) {
removeAsListenerAndDispose(projectController);
projectController = new ProjectController(urequest, this.getWindowControl(), userCourseEnv, courseNode, project, false, moduleConfig);
listenTo(projectController);
mainPanel.pushContent(projectController.getInitialComponent());
}
/**
*
* @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
*/
protected void doDispose() {
//
}
private TableController createTableController(final UserRequest ureq, WindowControl wControl) {
numberOfCustomFieldInTable = 0;
numberOfEventInTable = 0;
TableGuiConfiguration tableConfig = new TableGuiConfiguration();
tableConfig.setTableEmptyMessage(translate("projectlist.no.projects"));
tableConfig.setPreferencesOffered(true, "projectbrokerList");
tableConfig.setShowAllLinkEnabled(false);// Do not allow show all because many entries takes too long to render
removeAsListenerAndDispose(tableController);
tableController = new TableController(tableConfig, ureq, wControl, this.getTranslator(), true);
listenTo(tableController);
int dataColumn = 0;
tableController.addColumnDescriptor(new DefaultColumnDescriptor("projectlist.tableheader.title", dataColumn++, TABLE_ACTION_SHOW_DETAIL, getLocale()));
CustomRenderColumnDescriptor projectManagerDescriptor = new CustomRenderColumnDescriptor("projectlist.tableheader.account.manager", dataColumn++, TABLE_ACTION_ACCOUNT_MANAGER, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new ProjectManagerColumnRenderer()){
/**
* @see org.olat.core.gui.components.table.DefaultColumnDescriptor#compareTo(int, int)
*/
@Override
public int compareTo(int rowa, int rowb) {
return super.compareTo(rowa, rowb);
}
/**
* @see org.olat.core.gui.components.table.CustomRenderColumnDescriptor#renderValue(org.olat.core.gui.render.StringOutput, int, org.olat.core.gui.render.Renderer)
*/
@Override
public void renderValue(StringOutput sb, int row, Renderer renderer) {
Object val = getModelData(row);
String rowSt = Integer.toString(row); // to get info about row in Renderer!
getCustomCellRenderer().render(sb, renderer, val, getLocale(), getAlignment(), rowSt);
}
};
tableController.addColumnDescriptor(projectManagerDescriptor);
// Custom-Fields
List<CustomField> customFieldList = moduleConfig.getCustomFields();
for (Iterator<CustomField> iterator = customFieldList.iterator(); iterator.hasNext();) {
CustomField customField = iterator.next();
if (customField.isTableViewEnabled()) {
numberOfCustomFieldInTable++;
DefaultColumnDescriptor columnDescriptor = new DefaultColumnDescriptor(customField.getName(), dataColumn++,null, getLocale());
columnDescriptor.setTranslateHeaderKey(false);
tableController.addColumnDescriptor(columnDescriptor);
}
}
// Project Events
for (Project.EventType eventType : Project.EventType.values()) {
if (moduleConfig.isProjectEventEnabled(eventType) && moduleConfig.isProjectEventTableViewEnabled(eventType)) {
numberOfEventInTable ++;
tableController.addColumnDescriptor(new CustomRenderColumnDescriptor("projectlist.tableheader.event." + eventType.getI18nKey(), dataColumn++,
null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new ProjectEventColumnRenderer()));
}
}
tableController.addColumnDescriptor(new CustomRenderColumnDescriptor("projectlist.tableheader.state", dataColumn++,
null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new ProjectStateColumnRenderer()));
tableController.addColumnDescriptor(new DefaultColumnDescriptor("projectlist.tableheader.numbers", dataColumn++, null, getLocale()));
String selectCmd = userCourseEnv.isCourseReadOnly() ? null : TABLE_ACTION_SELECT;
tableController.addColumnDescriptor(new BooleanColumnDescriptor("projectlist.tableheader.select", dataColumn++, selectCmd,
translate("table.action.select"), "-" ));
String cancelCmd = userCourseEnv.isCourseReadOnly() ? null : TABLE_ACTION_CANCEL_SELECT;
tableController.addColumnDescriptor(new BooleanColumnDescriptor("projectlist.tableheader.cancel.select", dataColumn++, cancelCmd,
translate("projectlist.tableheader.cancel.select"), "-" ));
return tableController;
}
/**
* Is called when a project is deleted via group-management
* (ProjectBrokerManager.deleteGroupDataFor(BusinessGroup group) , DeletableGroupData-interface)
*
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
@Override
public void event(Event event) {
updateProjectListModelOf(tableController, getIdentity());
}
}