mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
* RM-630 (Definition for 'request for info' workflow)
* RM-631 (The records management team can create a rule to request information about an undeclared record) * RM-632 (The records management team can use an UI action and custom UI to start a "request for information" workflow about an undeclared record) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@48854 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -111,6 +111,9 @@
|
||||
<!-- Import disposition properties -->
|
||||
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-disposition-properties-context.xml"/>
|
||||
|
||||
<!-- Import workflows -->
|
||||
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-workflow-context.xml"/>
|
||||
|
||||
<!-- RM Script API -->
|
||||
|
||||
<bean id="scriptRecordsManagementService" parent="baseJavaScriptExtension" class="org.alfresco.module.org_alfresco_module_rm.jscript.ScriptRecordsManagmentService">
|
||||
|
@@ -723,4 +723,15 @@
|
||||
<property name="capability" value="ManageRules"/>
|
||||
</bean>
|
||||
|
||||
<bean id="jsonConversionComponent.requestInfo"
|
||||
parent="jsonConversionComponent.baseAction">
|
||||
<property name="name" value="requestInfo"/>
|
||||
<property name="kinds">
|
||||
<set>
|
||||
<value>RECORD</value>
|
||||
</set>
|
||||
</property>
|
||||
<property name="capability" value="RequestRecordInformation"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
@@ -0,0 +1,30 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||
<beans>
|
||||
<bean id="rm.workflowBootstrap" parent="workflowDeployer">
|
||||
<property name="workflowDefinitions">
|
||||
<!-- Request info workflow definition -->
|
||||
<list>
|
||||
<props>
|
||||
<prop key="engineId">activiti</prop>
|
||||
<prop key="location">alfresco/workflow/requestInfo.bpmn20.xml</prop>
|
||||
<prop key="mimetype">text/xml</prop>
|
||||
<prop key="redeploy">false</prop>
|
||||
</props>
|
||||
</list>
|
||||
</property>
|
||||
<property name="models">
|
||||
<list>
|
||||
<value>alfresco/workflow/rmWorkflowModel.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<!-- FIXME: Use labels -->
|
||||
<!--
|
||||
<property name="labels">
|
||||
<list>
|
||||
<value>alfresco/workflow/workflow-messages</value>
|
||||
</list>
|
||||
</property>
|
||||
-->
|
||||
</bean>
|
||||
</beans>
|
102
rm-server/config/alfresco/workflow/requestInfo.bpmn20.xml
Normal file
102
rm-server/config/alfresco/workflow/requestInfo.bpmn20.xml
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions
|
||||
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:activiti="http://activiti.org/bpmn"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
|
||||
typeLanguage="http://www.w3.org/2001/XMLSchema"
|
||||
expressionLanguage="http://www.w3.org/1999/XPath"
|
||||
targetNamespace="http://alfresco.org">
|
||||
|
||||
<process
|
||||
id="activitiRequestForInformation"
|
||||
name="Request For Information Activiti Workflow"
|
||||
isExecutable="true">
|
||||
|
||||
<startEvent
|
||||
id="start"
|
||||
activiti:formKey="rmwf:submitRequestInfoTask">
|
||||
</startEvent>
|
||||
|
||||
<sequenceFlow
|
||||
id="flow1"
|
||||
sourceRef="start"
|
||||
targetRef="requestInfoTask">
|
||||
</sequenceFlow>
|
||||
|
||||
<userTask
|
||||
id="requestInfoTask"
|
||||
name="Request Info Task"
|
||||
activiti:formKey="rmwf:requestInfoTask">
|
||||
<extensionElements>
|
||||
<activiti:taskListener
|
||||
event="create"
|
||||
class="org.alfresco.workflow.requestInfo.RequestInfoAssignmentHandler">
|
||||
</activiti:taskListener>
|
||||
<activiti:taskListener
|
||||
event="complete"
|
||||
class="org.alfresco.workflow.requestInfo.RequestInfoVariableHandler">
|
||||
</activiti:taskListener>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
|
||||
<sequenceFlow
|
||||
id="flow2"
|
||||
sourceRef="requestInfoTask"
|
||||
targetRef="reviewRequestInfoTask">
|
||||
</sequenceFlow>
|
||||
|
||||
<userTask
|
||||
id="reviewRequestInfoTask"
|
||||
name="Review Request Info Task"
|
||||
activiti:formKey="rmwf:reviewRequestInfoTask">
|
||||
<extensionElements>
|
||||
<activiti:taskListener
|
||||
event="create"
|
||||
class="org.alfresco.workflow.requestInfo.RequestInfoNotifier">
|
||||
</activiti:taskListener>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
|
||||
<sequenceFlow
|
||||
id="flow3"
|
||||
sourceRef="reviewRequestInfoTask"
|
||||
targetRef="theEnd">
|
||||
</sequenceFlow>
|
||||
|
||||
<endEvent
|
||||
id="theEnd">
|
||||
</endEvent>
|
||||
</process>
|
||||
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_activitiRequestForInformation">
|
||||
<bpmndi:BPMNPlane bpmnElement="activitiRequestForInformation" id="BPMNPlane_activitiRequestForInformation">
|
||||
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="30.0" y="200.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="reviewRequestInfoTask" id="BPMNShape_reviewRequestInfoTask">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="290.0" y="190.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="theEnd" id="BPMNShape_theEnd">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="455.0" y="200.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="requestInfoTask" id="BPMNShape_requestInfoTask">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="130.0" y="190.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="65.0" y="217.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="130.0" y="217.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="235.0" y="217.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="290.0" y="217.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="395.0" y="217.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="455.0" y="217.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
62
rm-server/config/alfresco/workflow/rmWorkflowModel.xml
Normal file
62
rm-server/config/alfresco/workflow/rmWorkflowModel.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<model name="rmwf:workflowmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
|
||||
|
||||
<imports>
|
||||
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
|
||||
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
|
||||
<import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm"/>
|
||||
</imports>
|
||||
|
||||
<namespaces>
|
||||
<namespace uri="http://www.alfresco.org/model/rmworkflow/1.0" prefix="rmwf"/>
|
||||
</namespaces>
|
||||
|
||||
<types>
|
||||
|
||||
<type name="rmwf:workflowTask">
|
||||
<parent>bpm:workflowTask</parent>
|
||||
<properties>
|
||||
<property name="rmwf:requestedInformation">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="rmwf:message">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
</properties>
|
||||
</type>
|
||||
|
||||
<type name="rmwf:submitRequestInfoTask">
|
||||
<parent>rmwf:workflowTask</parent>
|
||||
<associations>
|
||||
<association name="rmwf:mixedAssignees">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>cm:authority</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
</type>
|
||||
|
||||
<type name="rmwf:requestInfoTask">
|
||||
<parent>rmwf:workflowTask</parent>
|
||||
</type>
|
||||
|
||||
<type name="rmwf:reviewRequestInfoTask">
|
||||
<parent>rmwf:workflowTask</parent>
|
||||
<overrides>
|
||||
<property name="bpm:reassignable">
|
||||
<default>false</default>
|
||||
</property>
|
||||
</overrides>
|
||||
</type>
|
||||
</types>
|
||||
|
||||
</model>
|
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.workflow.requestInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.activiti.engine.delegate.DelegateTask;
|
||||
import org.activiti.engine.delegate.TaskListener;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
|
||||
import org.alfresco.repo.workflow.activiti.ActivitiScriptNodeList;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
* An assignment handler for the request info workflow.
|
||||
* An RM manager/admin can select one or more user(s) and/or
|
||||
* one or more group(s) when starting the request info workflow.
|
||||
* This assignment handler assigns for everyone a task (it is a pooled task).
|
||||
*
|
||||
* @author Tuna Aksoy
|
||||
* @since 2.1
|
||||
*/
|
||||
public class RequestInfoAssignmentHandler implements TaskListener
|
||||
{
|
||||
/**
|
||||
* @see org.activiti.engine.delegate.TaskListener#notify(org.activiti.engine.delegate.DelegateTask)
|
||||
*/
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask)
|
||||
{
|
||||
ParameterCheck.mandatory("delegateTask", delegateTask);
|
||||
|
||||
// Set the workflow description for the task
|
||||
// FIXME: I18N!!!
|
||||
// FIXME: Record name!!!
|
||||
delegateTask.setVariable("bpm_workflowDescription", "Information requested for record '" + "test.doc" + "'");
|
||||
|
||||
// Get the list of user(s) and/or group(s)
|
||||
ActivitiScriptNodeList usersAndGroups = (ActivitiScriptNodeList) delegateTask.getVariable("rmwf_mixedAssignees");
|
||||
|
||||
// Check if it was possible to extract the user(s) and/or group(s)
|
||||
if (usersAndGroups == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("It was not possible to extract the user(s) and/or group(s)!!!");
|
||||
}
|
||||
|
||||
// Define lists for candidate user(s)/group(s)
|
||||
List<String> candidateUsers = new ArrayList<String>();
|
||||
List<String> candidateGroups = new ArrayList<String>();
|
||||
|
||||
// Iterate through the list add user(s)/group(s) to the lists
|
||||
for (ActivitiScriptNode activitiScriptNode : usersAndGroups)
|
||||
{
|
||||
// Get the node type
|
||||
String type = activitiScriptNode.getType();
|
||||
// Get the properties
|
||||
Map<String, Object> properties = activitiScriptNode.getProperties();
|
||||
|
||||
// Check if it is a user or a group
|
||||
if (type.equalsIgnoreCase(ContentModel.TYPE_PERSON.toString()))
|
||||
{
|
||||
// Add the user
|
||||
candidateUsers.add((String) properties.get(ContentModel.PROP_USERNAME.toString()));
|
||||
}
|
||||
else if (type.equalsIgnoreCase(ContentModel.TYPE_AUTHORITY_CONTAINER.toString()))
|
||||
{
|
||||
// Add the group
|
||||
candidateGroups.add((String) properties.get(ContentModel.PROP_AUTHORITY_NAME.toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("The type '" + type + "' is neither a user nor a group!!!");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is at least one user or one group
|
||||
if (candidateUsers.size() == 0 && candidateGroups.size() == 0)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Neither a user nor a group was found!!!");
|
||||
}
|
||||
|
||||
// Add the user(s) to the task
|
||||
if (candidateUsers.size() > 0)
|
||||
{
|
||||
delegateTask.addCandidateUsers(candidateUsers);
|
||||
}
|
||||
|
||||
// Add the group(s) to the task
|
||||
if (candidateGroups.size() > 0)
|
||||
{
|
||||
delegateTask.addCandidateGroups(candidateGroups);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.workflow.requestInfo;
|
||||
|
||||
import org.activiti.engine.delegate.DelegateTask;
|
||||
import org.activiti.engine.delegate.TaskListener;
|
||||
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.activiti.engine.impl.context.Context;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.notification.EMailNotificationProvider;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
|
||||
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.notification.NotificationContext;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
* Request info workflow notifier.
|
||||
* After the pooled task has been finished the initiator of the workflow will
|
||||
* get a task to verify the information. The initiator will also receive an email.
|
||||
*
|
||||
* @author Tuna Aksoy
|
||||
* @since v2.1
|
||||
*/
|
||||
public class RequestInfoNotifier implements TaskListener
|
||||
{
|
||||
/**
|
||||
* @see org.activiti.engine.delegate.TaskListener#notify(org.activiti.engine.delegate.DelegateTask)
|
||||
*/
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask)
|
||||
{
|
||||
ParameterCheck.mandatory("delegateTask", delegateTask);
|
||||
|
||||
// Set the workflow description for the task
|
||||
// FIXME: I18N!!!
|
||||
// FIXME: Record name!!!
|
||||
delegateTask.setVariable("bpm_workflowDescription", "Information provided for record '" + "test.doc" + "'");
|
||||
|
||||
// Assign the task to the initiator
|
||||
String initiator = getInitiator(delegateTask);
|
||||
delegateTask.setAssignee(initiator);
|
||||
|
||||
// Create the context and send an email to the initiator
|
||||
NotificationContext notificationContext = new NotificationContext();
|
||||
notificationContext.setAsyncNotification(true);
|
||||
notificationContext.setIgnoreNotificationFailure(true);
|
||||
notificationContext.addTo(initiator);
|
||||
// FIXME: I18N!!! and get the record name and the user name who provided the information
|
||||
notificationContext.setSubject("Information provided for the record '" + "" + "'.");
|
||||
notificationContext.setBody("The user '" + "' has provided the needed information for the record '" + "" + "'.");
|
||||
|
||||
// Send the email
|
||||
getServiceRegistry().getNotificationService().sendNotification(EMailNotificationProvider.NAME, notificationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract the initiator from the task
|
||||
*
|
||||
* @param delegateTask The delegate task
|
||||
* @return Returns the initiator of the workflow. If the initiator does not exist the admin user will be returned.
|
||||
*/
|
||||
private String getInitiator(DelegateTask delegateTask)
|
||||
{
|
||||
String userName = null;
|
||||
ActivitiScriptNode initiator = (ActivitiScriptNode) delegateTask.getVariable("initiator");
|
||||
if (initiator.exists())
|
||||
{
|
||||
userName = (String) initiator.getProperties().get(ContentModel.PROP_USERNAME.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
userName = AuthenticationUtil.getAdminUserName();
|
||||
}
|
||||
return userName;
|
||||
}
|
||||
|
||||
//FIXME: Is there a better way to call services?
|
||||
|
||||
/**
|
||||
* Helper method for getting the service registry in order to call services
|
||||
*
|
||||
* @return Returns the service registry
|
||||
*/
|
||||
private ServiceRegistry getServiceRegistry()
|
||||
{
|
||||
ProcessEngineConfigurationImpl config = Context.getProcessEngineConfiguration();
|
||||
if (config != null)
|
||||
{
|
||||
// Fetch the registry that is injected in the activiti spring-configuration
|
||||
ServiceRegistry registry = (ServiceRegistry) config.getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
|
||||
if (registry == null)
|
||||
{
|
||||
throw new RuntimeException(
|
||||
"Service-registry not present in ProcessEngineConfiguration beans, expected ServiceRegistry with key" +
|
||||
ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
throw new IllegalStateException("No ProcessEngineCOnfiguration found in active context");
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.workflow.requestInfo;
|
||||
|
||||
import org.activiti.engine.delegate.DelegateExecution;
|
||||
import org.activiti.engine.delegate.DelegateTask;
|
||||
import org.activiti.engine.delegate.TaskListener;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
* A variable handler for saving the task variables to the execution context.
|
||||
* Some of the information will be needed in other tasks (e.g. "rmwf_message").
|
||||
* This variable handler saves the local task variable to the execution context.
|
||||
*
|
||||
* @author Tuna Aksoy
|
||||
* @since 2.1
|
||||
*/
|
||||
public class RequestInfoVariableHandler implements TaskListener
|
||||
{
|
||||
/**
|
||||
* @see org.activiti.engine.delegate.TaskListener#notify(org.activiti.engine.delegate.DelegateTask)
|
||||
*/
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask)
|
||||
{
|
||||
ParameterCheck.mandatory("delegateTask", delegateTask);
|
||||
|
||||
// Save the variable from the task
|
||||
DelegateExecution execution = delegateTask.getExecution();
|
||||
execution.setVariable("rmwf_message", delegateTask.getVariable("rmwf_message"));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user