Skip to content
Snippets Groups Projects
Commit c5bf1dc6 authored by srosse's avatar srosse
Browse files

OO-2042: add a print view to the member list element

parent e638a5cf
No related branches found
No related tags found
No related merge requests found
Showing
with 274 additions and 53 deletions
......@@ -24,7 +24,6 @@ import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.FormUIFactory;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.modules.IModuleConfiguration;
/**
......@@ -38,7 +37,7 @@ public abstract class BasicFormFragment implements IFormFragment {
protected IFormFragmentHost host;
protected IFormFragmentContainer container;
public BasicFormFragment(WindowControl wControl) {
public BasicFormFragment() {
CoreSpringFactory.autowireObject(this);
}
......
......@@ -90,7 +90,9 @@ public abstract class FormBasicController extends BasicController implements IFo
protected StackedPanel initialPanel;
protected FormUIFactory uifactory = FormUIFactory.getInstance();
private List<FragmentRef> fragments;
protected final FormUIFactory uifactory = FormUIFactory.getInstance();
public FormBasicController(UserRequest ureq, WindowControl wControl) {
this(ureq, wControl, (String)null);
......@@ -674,8 +676,8 @@ public abstract class FormBasicController extends BasicController implements IFo
public FragmentRef(IFormFragment referent) {
super(referent);
}
}
private List<FragmentRef> fragments = new ArrayList<>();
}
@Override
public void setNeedsLayout() {
flc.setDirty(true);
......@@ -683,6 +685,9 @@ public abstract class FormBasicController extends BasicController implements IFo
@Override
public void registerFormFragment(IFormFragment fragment) {
if(fragments == null) {
fragments = new ArrayList<>(5);
}
fragments.add(new FragmentRef(fragment));
}
......@@ -706,6 +711,8 @@ public abstract class FormBasicController extends BasicController implements IFo
@Override
public void forEachFragment(Consumer<IFormFragment> handler) {
if(fragments == null) return;
fragments.stream()
.filter(ref -> ref.get() != null)
.forEach(ref -> {
......@@ -717,6 +724,8 @@ public abstract class FormBasicController extends BasicController implements IFo
}
protected boolean routeEventToFragments(UserRequest ureq, Component source, Event event) {
if(fragments == null) return false;
// implement shortcut?!
Boolean processed =
fragments.stream()
......@@ -731,6 +740,8 @@ public abstract class FormBasicController extends BasicController implements IFo
protected boolean routeEventToFragments(UserRequest ureq, Controller source, Event event) {
if(fragments == null) return false;
// implement shortcut?!
Boolean processed =
fragments.stream()
......
......@@ -32,15 +32,14 @@ import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
import org.olat.core.gui.components.form.flexible.impl.BasicFormFragment;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.IFormFragment;
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.generic.closablewrapper.CloseableModalController;
import org.olat.core.util.StringHelper;
import org.olat.course.condition.AreaSelectionController;
import org.olat.course.condition.GroupSelectionController;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.BusinessGroupService;
import org.olat.group.BusinessGroupShort;
import org.olat.group.area.BGArea;
......@@ -72,7 +71,7 @@ public class MembersSelectorFormFragment extends BasicFormFragment {
public static final String CONFIG_KEY_PARTICIPANTS_ALL = "ParticipantsAll";
private CourseEditorEnv cev;
private final CourseEditorEnv cev;
// Coaches
private SelectionElement wantCoaches;
......@@ -108,9 +107,8 @@ public class MembersSelectorFormFragment extends BasicFormFragment {
@Autowired
private BusinessGroupService businessGroupService;
public MembersSelectorFormFragment(UserRequest ureq, WindowControl wControl, UserCourseEnvironment uce) {
super(wControl);
this.cev = uce.getCourseEditorEnv();
public MembersSelectorFormFragment(CourseEditorEnv cev) {
this.cev = cev;
}
@Override
......
......@@ -119,15 +119,15 @@ public class COConfigForm extends FormBasicController {
this.config = config;
// this.cev = uce.getCourseEditorEnv();
this.membersFragment = new MembersSelectorFormFragment(ureq, wControl, uce);
membersFragment = new MembersSelectorFormFragment(uce.getCourseEditorEnv());
registerFormFragment(membersFragment); // register with parent for proper lifecycle handling
initForm(ureq);
this.validateFormLogic(ureq);
validateFormLogic(ureq);
}
@Override
public void storeFormData(UserRequest ureq) {
this.membersFragment.storeConfiguration(ureq, IModuleConfiguration.fragment("emailTo", "", config));
membersFragment.storeConfiguration(ureq, IModuleConfiguration.fragment("emailTo", "", config));
}
/**
......
......@@ -19,7 +19,14 @@
*/
package org.olat.course.nodes.members;
import java.util.List;
import java.util.Locale;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.id.Identity;
import org.olat.core.id.UserConstants;
import org.olat.user.UserPropertiesRow;
import org.olat.user.propertyhandlers.UserPropertyHandler;
/**
*
......@@ -27,7 +34,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class Member {
public class Member extends UserPropertiesRow {
private final String firstName;
private final String lastName;
......@@ -41,11 +48,12 @@ public class Member {
private FormLink idLink;
private FormLink removeLink;
public Member(Long key, String firstName, String lastName, String fullName, boolean portrait, String portraitCssClass) {
this.firstName = firstName;
this.lastName = lastName;
public Member(Identity identity, String fullName, List<UserPropertyHandler> userPropertyHandlers, Locale locale, boolean portrait, String portraitCssClass) {
super(identity, userPropertyHandlers, locale);
this.firstName = identity.getUser().getProperty(UserConstants.FIRSTNAME, locale);;
this.lastName = identity.getUser().getProperty(UserConstants.LASTNAME, locale);
this.fullName = fullName;
this.key = key;
this.key = identity.getKey();
this.portrait = portrait;
this.portraitCssClass = portraitCssClass;
}
......
......@@ -77,7 +77,7 @@ public class MembersConfigForm extends FormBasicController {
ModuleConfiguration config) {
super(ureq, wControl);
this.config = config;
this.membersFragment = new MembersSelectorFormFragment(ureq, wControl, euce);
membersFragment = new MembersSelectorFormFragment(euce.getCourseEditorEnv());
registerFormFragment(membersFragment); // register with parent for proper lifecycle handling
initForm(ureq);
validateFormLogic(ureq);
......@@ -177,21 +177,21 @@ public class MembersConfigForm extends FormBasicController {
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
/*boolean processed = */this.membersFragment.processFormEvent(ureq, source, event);
membersFragment.processFormEvent(ureq, source, event);
if(showOwners == source) { // || showCoaches == source || showParticipants == source) {
config.setBooleanEntry(MembersCourseNode.CONFIG_KEY_SHOWOWNER, showOwners.isSelected(0));
update();
fireEvent(ureq, Event.CHANGED_EVENT);
} // else
if(emailFunctionEl == source && emailFunctionEl.isOneSelected()) {
if(emailFunctionEl.isSelected(0)) {
config.setStringValue(MembersCourseNode.CONFIG_KEY_EMAIL_FUNCTION, MembersCourseNode.EMAIL_FUNCTION_ALL);
} else {
config.setStringValue(MembersCourseNode.CONFIG_KEY_EMAIL_FUNCTION, MembersCourseNode.EMAIL_FUNCTION_COACH_ADMIN);
} else if(emailFunctionEl == source) {
if(emailFunctionEl.isOneSelected()) {
if(emailFunctionEl.isSelected(0)) {
config.setStringValue(MembersCourseNode.CONFIG_KEY_EMAIL_FUNCTION, MembersCourseNode.EMAIL_FUNCTION_ALL);
} else {
config.setStringValue(MembersCourseNode.CONFIG_KEY_EMAIL_FUNCTION, MembersCourseNode.EMAIL_FUNCTION_COACH_ADMIN);
}
fireEvent(ureq, Event.CHANGED_EVENT);
}
fireEvent(ureq, Event.CHANGED_EVENT);
}
update();
......
......@@ -31,7 +31,9 @@ import java.util.Set;
import org.olat.NewControllerFactory;
import org.olat.basesecurity.BaseSecurity;
import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
......@@ -39,14 +41,16 @@ import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.link.LinkPopupSettings;
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.creator.ControllerCreator;
import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.User;
import org.olat.core.id.UserConstants;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.id.context.BusinessControlFactory;
......@@ -73,6 +77,7 @@ import org.olat.repository.RepositoryService;
import org.olat.user.DisplayPortraitManager;
import org.olat.user.UserAvatarMapper;
import org.olat.user.UserManager;
import org.olat.user.propertyhandlers.UserPropertyHandler;
import org.springframework.beans.factory.annotation.Autowired;
/**
......@@ -85,11 +90,15 @@ import org.springframework.beans.factory.annotation.Autowired;
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*/
public class MembersCourseNodeRunController extends FormBasicController {
private final List<UserPropertyHandler> userPropertyHandlers;
public static final String USER_PROPS_ID = MembersCourseNodeRunController.class.getName();
private final CourseEnvironment courseEnv;
private final DisplayPortraitManager portraitManager;
private final String avatarBaseURL;
private Link printLink;
private FormLink allEmailLink;
private List<Member> ownerList;
......@@ -122,13 +131,14 @@ public class MembersCourseNodeRunController extends FormBasicController {
@Autowired
private BusinessGroupService businessGroupService;
final ModuleConfiguration config;
private final ModuleConfiguration config;
public MembersCourseNodeRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, ModuleConfiguration config) {
super(ureq, wControl, "members");
this.config = config;
userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, false);
courseEnv = userCourseEnv.getCourseEnvironment();
avatarBaseURL = registerCacheableMapper(ureq, "avatars-members", new UserAvatarMapper(true));
portraitManager = DisplayPortraitManager.getInstance();
......@@ -141,13 +151,19 @@ public class MembersCourseNodeRunController extends FormBasicController {
String emailFct = config.getStringValue(MembersCourseNode.CONFIG_KEY_EMAIL_FUNCTION, MembersCourseNode.EMAIL_FUNCTION_COACH_ADMIN);
canEmail = MembersCourseNode.EMAIL_FUNCTION_ALL.equals(emailFct) || userCourseEnv.isAdmin() || userCourseEnv.isCoach();
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
if(formLayout instanceof FormLayoutContainer) {
printLink = LinkFactory.createButton("print", ((FormLayoutContainer)formLayout).getFormItemComponent(), this);
printLink.setIconLeftCSS("o_icon o_icon_print o_icon-lg");
printLink.setPopup(new LinkPopupSettings(880, 500, "print-members"));
((FormLayoutContainer)formLayout).getFormItemComponent().put("print", printLink);
}
IModuleConfiguration membersFrag = IModuleConfiguration.fragment("members", config);
List<Identity> owners;
......@@ -303,9 +319,6 @@ public class MembersCourseNodeRunController extends FormBasicController {
}
private Member createMember(Identity identity) {
User user = identity.getUser();
String firstname = user.getProperty(UserConstants.FIRSTNAME, null);
String lastname = user.getProperty(UserConstants.LASTNAME, null);
MediaResource rsrc = portraitManager.getSmallPortraitResource(identity.getName());
String portraitCssClass = null;
......@@ -318,7 +331,7 @@ public class MembersCourseNodeRunController extends FormBasicController {
portraitCssClass = DisplayPortraitManager.DUMMY_BIG_CSS_CLASS;
}
String fullname = userManager.getUserDisplayName(identity);
return new Member(identity.getKey(), firstname, lastname, fullname, rsrc != null, portraitCssClass);
return new Member(identity, fullname, userPropertyHandlers, getLocale(), rsrc != null, portraitCssClass);
}
@Override
......@@ -330,6 +343,16 @@ public class MembersCourseNodeRunController extends FormBasicController {
protected void formOK(UserRequest ureq) {
//
}
@Override
public void event(UserRequest ureq, Component source, Event event) {
if(source == printLink) {
doPrint(ureq);
}
super.event(ureq, source, event);
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
......@@ -376,7 +399,7 @@ public class MembersCourseNodeRunController extends FormBasicController {
cmc = null;
}
protected void doEmail(UserRequest ureq) {
private void doEmail(UserRequest ureq) {
if(mailCtrl != null || cmc != null) return;
removeAsListenerAndDispose(cmc);
removeAsListenerAndDispose(mailCtrl);
......@@ -391,23 +414,20 @@ public class MembersCourseNodeRunController extends FormBasicController {
cmc.activate();
}
protected void doOpenChat(Member member, UserRequest ureq) {
private void doOpenChat(Member member, UserRequest ureq) {
Buddy buddy = imService.getBuddyById(member.getKey());
OpenInstantMessageEvent e = new OpenInstantMessageEvent(ureq, buddy);
ureq.getUserSession().getSingleUserEventCenter().fireEventToListenersOf(e, InstantMessagingService.TOWER_EVENT_ORES);
}
protected void doSendEmailToMember(UserRequest ureq, FormItem source, FormEvent event, Member member) {
doSendEmailToMember(member, ureq);
}
protected void doSendEmailToMember(Member member, UserRequest ureq) {
private void doSendEmailToMember(Member member, UserRequest ureq) {
ContactList memberList = new ContactList(translate("members.to", new String[]{ member.getFullName(), courseEnv.getCourseTitle() }));
Identity identity = securityManager.loadIdentityByKey(member.getKey());
memberList.add(identity);
doSendEmailToMember(memberList, ureq);
}
protected void doSendEmailToMember(ContactList contactList, UserRequest ureq) {
private void doSendEmailToMember(ContactList contactList, UserRequest ureq) {
if (contactList.getEmailsAsStrings().size() > 0) {
removeAsListenerAndDispose(cmc);
removeAsListenerAndDispose(emailController);
......@@ -436,13 +456,26 @@ public class MembersCourseNodeRunController extends FormBasicController {
return translate("email.body.template", new String[]{courseName, courseLink.toString()});
}
protected void doOpenHomePage(Member member, UserRequest ureq) {
private void doOpenHomePage(Member member, UserRequest ureq) {
String url = "[HomePage:" + member.getKey() + "]";
BusinessControl bc = BusinessControlFactory.getInstance().createFromString(url);
WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
NewControllerFactory.getInstance().launch(ureq, bwControl);
}
private void doPrint(UserRequest ureq) {
ControllerCreator printControllerCreator = new ControllerCreator() {
@Override
public Controller createController(UserRequest lureq, WindowControl lwControl) {
lwControl.getWindowBackOffice().getChiefController().addBodyCssClass("o_cmembers_print");
return new MembersPrintController(lureq, lwControl, courseEnv, avatarBaseURL, userPropertyHandlers,
ownerList, coachList, participantList);
}
};
ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createPrintPopupLayout(printControllerCreator);
openInNewBrowserWindow(ureq, layoutCtrlr);
}
public static class IdentityComparator implements Comparator<Identity> {
@Override
......@@ -466,5 +499,4 @@ public class MembersCourseNodeRunController extends FormBasicController {
return result;
}
}
}
......@@ -70,10 +70,10 @@ public class MembersPeekViewController extends BasicController {
courseEnv = userCourseEnv.getCourseEnvironment();
readFormData(config);
initForm(ureq, userCourseEnv);
initForm(ureq);
}
private void initForm(UserRequest ureq, UserCourseEnvironment courseEnv) {
private void initForm(UserRequest ureq) {
TableGuiConfiguration tableConfig = new TableGuiConfiguration();
tableConfig.setDisplayTableHeader(false);
......
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <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 the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <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>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.course.nodes.members;
import java.util.List;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.panel.MainPanel;
import org.olat.core.gui.components.velocity.VelocityContainer;
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.course.run.environment.CourseEnvironment;
import org.olat.user.propertyhandlers.UserPropertyHandler;
/**
*
* Initial date: 30.05.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class MembersPrintController extends BasicController {
private final String avatarBaseURL;
private final VelocityContainer mainVC;
private final List<UserPropertyHandler> userPropertyHandlers;
public MembersPrintController(UserRequest ureq, WindowControl wControl,
CourseEnvironment courseEnv, String avatarBaseURL, List<UserPropertyHandler> userPropertyHandlers,
List<Member> owners, List<Member> coaches, List<Member> participants) {
super(ureq, wControl);
this.avatarBaseURL = avatarBaseURL;
this.userPropertyHandlers = userPropertyHandlers;
mainVC = createVelocityContainer("print");
mainVC.contextPut("courseTitle", courseEnv.getCourseTitle());
mainVC.contextPut("avatarBaseURL", avatarBaseURL);
mainVC.contextPut("userPropertyHandlers", userPropertyHandlers);
if(owners != null && owners.size() > 0) {
initFormMemberList("owners", owners);
}
if(coaches != null && coaches.size() > 0) {
initFormMemberList("coaches", coaches);
}
if(participants != null && participants.size() > 0) {
initFormMemberList("participants", participants);
}
MainPanel mainPanel = new MainPanel("membersPrintPanel");
mainPanel.setContent(mainVC);
putInitialPanel(mainPanel);
}
private void initFormMemberList(String name, List<Member> members) {
VelocityContainer listVC = createVelocityContainer("printList");
listVC.contextPut("avatarBaseURL", avatarBaseURL);
listVC.contextPut("members", members);
listVC.contextPut("userPropertyHandlers", userPropertyHandlers);
mainVC.put(name, listVC);
}
@Override
protected void event(UserRequest ureq, Component source, Event event) {
//
}
@Override
protected void doDispose() {
//
}
}
......@@ -2,6 +2,9 @@
#if($r.available("email"))
$r.render("email")
#end
#if($r.available("print"))
$r.render("print")
#end
</div>
<div class="o_cmembers">
......
<div id="o_print_brand" class="clearfix">
<span class="o_navbar-brand"></span>
</div>
<h1>$r.translate("print.title")</h1>
<h3>$courseTitle</h3>
#if($r.available("owners"))
$r.render("owners")
#end
#if($r.available("coaches"))
$r.render("coaches")
#end
#if($r.available("participants"))
$r.render("participants")
#end
<div class="clearfix"> </div>
<script type='text/javascript'>
/* <![CDATA[ */
jQuery(function() {
var bgImg = jQuery("#o_print_brand .o_navbar-brand");
var imgUrl = bgImg.css("background-image");
if(!(typeof imgUrl === "undefined") && !(imgUrl == "none")) {
imgUrl = imgUrl.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
if(imgUrl.length > 12) {
jQuery("#o_print_brand").empty().append(jQuery('<img src="' + imgUrl + '" />'));
} else {
jQuery("#o_print_brand").empty();
}
} else {
jQuery("#o_print_brand").empty();
}
})
window.print();
/* ]]> */
</script>
\ No newline at end of file
#foreach($member in $members)
<div class="o_cmember col-xs-3 col-sm-3 col-md-3">
<div class="clearfix">
<div class="o_portrait">
<img src="$avatarBaseURL/$member.getKey()/portrait.jpg">
</div>
<div class="o_cmember_info_wrapper">
<strong>$member.fullName</strong>
#foreach($userPropertyHandler in $userPropertyHandlers)
#if($userPropertyHandler.name != "firstName" && $userPropertyHandler.name != "lastName")
<br>${member.getIdentityProp($foreach.index)}
#end
#end
</div>
</div>
</div>
#end
......@@ -35,6 +35,8 @@ owners.to=Aministratoren von Kurs "{0}"
pane.tab.accessibility=Zugang
pane.tab.membersconfig=Konfiguration
participants.to=Teilnehmer von Kurs "{0}"
print.function=Drcken
print.title=Klassenspiegel
select.members=Benutzer ausw\u00E4hlen
title_info=Teilnehmerliste
......
......@@ -323,6 +323,29 @@
</property>
</bean>
</entry>
<entry key="org.olat.course.nodes.members.MembersCourseNodeRunController">
<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
<property name="description" value="Print view in the members course element." />
<property name="propertyHandlers">
<list>
<ref bean="userPropertyFirstName" />
<ref bean="userPropertyLastName" />
<ref bean="userPropertyEmail" />
</list>
</property>
<property name="adminViewOnlyProperties">
<set></set>
</property>
<property name="mandatoryProperties">
<set>
<ref bean="userPropertyFirstName" />
<ref bean="userPropertyLastName" />
<ref bean="userPropertyEmail" />
</set>
</property>
</bean>
</entry>
<entry key="org.olat.ims.qti.export.QTIExportFormatterCSVType1">
<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
......
......@@ -178,6 +178,10 @@
}
}
.o_cmembers_print #o_print_brand img {
float: right;
}
.tag.label.label-info {
margin-right: 3px;
}
......
This diff is collapsed.
source diff could not be displayed: it is too large. Options to address this: view the blob.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment