/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Text;
using System.Text.RegularExpressions;
using ASC.Api.Core;
using ASC.Api.CRM;
using ASC.Api.Documents;
using ASC.Common.Web;
using ASC.Core;
using ASC.CRM.ApiModels;
using ASC.CRM.Core;
using ASC.CRM.Core.Dao;
using ASC.CRM.Core.Entities;
using ASC.CRM.Core.Enums;
using ASC.MessagingSystem;
using ASC.Web.Api.Routing;
using ASC.Web.CRM.Services.NotifyService;
using ASC.Web.Files.Classes;
using ASC.Web.Files.Utils;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OrderBy = ASC.CRM.Core.Entities.OrderBy;
namespace ASC.CRM.Api
{
public class RelationshipEventsController : BaseApiController
{
private readonly FileUploader _fileUploader;
private readonly ASC.Files.Core.Data.DaoFactory _filesDaoFactory;
private readonly FileWrapperHelper _fileWrapperHelper;
private readonly FilesSettingsHelper _filesSettingsHelper;
private readonly ApiContext _apiContext;
private readonly MessageService _messageService;
private readonly MessageTarget _messageTarget;
private readonly SecurityContext _securityContext;
private readonly NotifyClient _notifyClient;
public RelationshipEventsController(
CrmSecurity crmSecurity,
DaoFactory daoFactory,
ApiContext apiContext,
MessageTarget messageTarget,
MessageService messageService,
FileWrapperHelper fileWrapperHelper,
ASC.Files.Core.Data.DaoFactory filesDaoFactory,
FileUploader fileUploader,
SecurityContext securityContext,
NotifyClient notifyClient,
FilesSettingsHelper filesSettingsHelper,
IMapper mapper)
: base(daoFactory, crmSecurity, mapper)
{
_apiContext = apiContext;
_messageTarget = messageTarget;
_messageService = messageService;
_fileWrapperHelper = fileWrapperHelper;
_filesDaoFactory = filesDaoFactory;
_fileUploader = fileUploader;
_securityContext = securityContext;
_notifyClient = notifyClient;
_filesSettingsHelper = filesSettingsHelper;
}
///
/// Returns the list of all events matching the parameters specified in the request
///
///
/// Get event list
///
/// History
/// Related entity type
/// Related entity ID
/// Task category ID
/// Event author
/// Earliest task due date
/// Latest task due date
///
/// Event list
///
[Read(@"history/filter")]
public IEnumerable GetHistory(
string entityType,
int entityId,
int categoryId,
Guid createBy,
ApiDateTime fromDate,
ApiDateTime toDate)
{
var entityTypeObj = ToEntityType(entityType);
switch (entityTypeObj)
{
case EntityType.Contact:
var contact = _daoFactory.GetContactDao().GetByID(entityId);
if (contact == null || !_crmSecurity.CanAccessTo(contact))
throw new ItemNotFoundException();
break;
case EntityType.Case:
var cases = _daoFactory.GetCasesDao().GetByID(entityId);
if (cases == null || !_crmSecurity.CanAccessTo(cases))
throw new ItemNotFoundException();
break;
case EntityType.Opportunity:
var deal = _daoFactory.GetDealDao().GetByID(entityId);
if (deal == null || !_crmSecurity.CanAccessTo(deal))
throw new ItemNotFoundException();
break;
default:
if (entityId != 0)
{
throw new ArgumentException();
}
break;
}
RelationshipEventByType eventByType;
IEnumerable result;
OrderBy eventOrderBy;
if (ASC.CRM.Classes.EnumExtension.TryParse(_apiContext.SortBy, true, out eventByType))
{
eventOrderBy = new OrderBy(eventByType, !_apiContext.SortDescending);
}
else if (string.IsNullOrEmpty(_apiContext.SortBy))
{
eventOrderBy = new OrderBy(RelationshipEventByType.Created, false);
}
else
{
eventOrderBy = null;
}
if (eventOrderBy != null)
{
result = ToListRelationshipEventDto(_daoFactory.GetRelationshipEventDao().GetItems(
_apiContext.FilterValue,
entityTypeObj,
entityId,
createBy,
categoryId,
fromDate,
toDate,
(int)_apiContext.StartIndex,
(int)_apiContext.Count,
eventOrderBy));
_apiContext.SetDataPaginated();
_apiContext.SetDataFiltered();
_apiContext.SetDataSorted();
}
else
{
result = ToListRelationshipEventDto(_daoFactory.GetRelationshipEventDao().GetItems(
_apiContext.FilterValue,
entityTypeObj,
entityId,
createBy,
categoryId,
fromDate,
toDate,
0,
0,
null));
}
return result;
}
///
/// Deletes the event with the ID specified in the request and all the files associated with this event
///
///
/// Delete event and related files
///
/// History
/// Event ID
///
///
///
/// Event
///
[Delete(@"history/{id:int}")]
public RelationshipEventDto DeleteHistory(int id)
{
if (id <= 0) throw new ArgumentException();
var item = _daoFactory.GetRelationshipEventDao().GetByID(id);
if (item == null) throw new ItemNotFoundException();
var wrapper = _mapper.Map(item);
_daoFactory.GetRelationshipEventDao().DeleteItem(id);
var messageAction = GetHistoryDeletedAction(item.EntityType, item.ContactID);
var entityTitle = wrapper.Contact == null ? wrapper.Entity.EntityTitle : wrapper.Contact.DisplayName;
_messageService.Send(messageAction, _messageTarget.Create(item.ID), entityTitle, wrapper.Category.Title);
return wrapper;
}
///
/// Creates a text (.txt) file in the selected folder with the title and contents sent in the request
///
/// Create txt
/// Files
/// Entity type
/// Entity ID
/// File title
/// File contents
///
/// File info
///
[Create(@"{entityType:regex(contact|opportunity|case)}/{entityid:int}/files/text")]
public FileWrapper CreateTextFile(
[FromRoute] string entityType,
[FromRoute] int entityid,
[FromBody] RelationshipEventCreateTextFileRequestDto inDto)
{
var title = inDto.Title;
var content = inDto.Content;
if (title == null) throw new ArgumentNullException("title");
if (content == null) throw new ArgumentNullException("content");
var folderid = GetRootFolderID();
FileWrapper result;
var extension = ".txt";
if (!string.IsNullOrEmpty(content))
{
if (Regex.IsMatch(content, @"<([^\s>]*)(\s[^<]*)>"))
{
extension = ".html";
}
}
using (var memStream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
title = title.EndsWith(extension, StringComparison.OrdinalIgnoreCase) ? title : (title + extension);
result = SaveFile(folderid, memStream, title);
}
AttachFiles(entityType, entityid, new List { (int)result.Id });
return result;
}
///
/// Upload file
///
/// Upload file
/// Files
///
///
/// Single file upload. You should set Content-Type & Content-Disposition header to specify filename and content type, and send file in request body
/// Using standart multipart/form-data method
/// ]]>
///
/// Entity type
/// Entity ID
/// Request Input stream
/// Content-Type Header
/// Content-Disposition Header
/// List of files when posted as multipart/form-data
/// If True, upload documents in original formats as well
///
/// File info
///
[Create(@"{entityType:regex(contact|opportunity|case)}/{entityid:int}/files/upload")]
public FileWrapper UploadFileInCRM([FromBody] UploadFileInCRMRequestDto inDto)
{
string entityType = inDto.EntityType;
int entityid = inDto.Entityid;
Stream file = inDto.File;
ContentType contentType = inDto.ContentType;
ContentDisposition contentDisposition = inDto.ContentDisposition;
IEnumerable files = inDto.Files;
bool storeOriginalFileFlag = inDto.StoreOriginalFileFlag;
_filesSettingsHelper.StoreOriginalFiles = storeOriginalFileFlag;
var folderid = GetRootFolderID();
var fileNames = new List();
FileWrapper uploadedFile = null;
if (files != null && files.Any())
{
//For case with multiple files
foreach (var postedFile in files)
{
using var fileStream = postedFile.OpenReadStream();
uploadedFile = SaveFile(folderid, fileStream, postedFile.FileName);
fileNames.Add(uploadedFile.Title);
}
}
else if (file != null)
{
uploadedFile = SaveFile(folderid, file, contentDisposition.FileName);
fileNames.Add(uploadedFile.Title);
}
return uploadedFile;
}
private FileWrapper SaveFile(int folderid, Stream file, string fileName)
{
var resultFile = _fileUploader.Exec(folderid, fileName, file.Length, file);
return _fileWrapperHelper.Get(resultFile);
}
///
/// Creates the event with the parameters specified in the request
///
///
/// Create event
///
/// History
/// Contact ID
/// Related entity type
/// Related entity ID
///
///
///
/// Contents
/// Category ID
/// Event creation date
/// List of IDs of the files associated with the event
/// User field list
///
/// Created event
///
[Create(@"history")]
public RelationshipEventDto AddHistoryTo([FromBody] AddHistoryToRequestDto inDto)
{
string entityType = inDto.EntityType;
int entityId = inDto.EntityId;
int contactId = inDto.ContactId;
string content = inDto.Content;
int categoryId = inDto.CategoryId;
ApiDateTime created = inDto.Created;
IEnumerable fileId = inDto.FileId;
IEnumerable notifyUserList = inDto.NotifyUserList;
if (!string.IsNullOrEmpty(entityType) &&
!(
string.Compare(entityType, "opportunity", StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(entityType, "case", StringComparison.OrdinalIgnoreCase) == 0)
)
throw new ArgumentException();
var entityTypeObj = ToEntityType(entityType);
var entityTitle = "";
if (contactId > 0)
{
var contact = _daoFactory.GetContactDao().GetByID(contactId);
if (contact == null || !_crmSecurity.CanAccessTo(contact))
throw new ArgumentException();
entityTitle = contact.GetTitle();
}
if (entityTypeObj == EntityType.Case)
{
var cases = _daoFactory.GetCasesDao().GetByID(entityId);
if (cases == null || !_crmSecurity.CanAccessTo(cases))
throw new ArgumentException();
if (contactId <= 0)
{
entityTitle = cases.Title;
}
}
if (entityTypeObj == EntityType.Opportunity)
{
var deal = _daoFactory.GetDealDao().GetByID(entityId);
if (deal == null || !_crmSecurity.CanAccessTo(deal))
throw new ArgumentException();
if (contactId <= 0)
{
entityTitle = deal.Title;
}
}
var relationshipEvent = new RelationshipEvent
{
CategoryID = categoryId,
EntityType = entityTypeObj,
EntityID = entityId,
Content = content,
ContactID = contactId,
CreateOn = created,
CreateBy = _securityContext.CurrentAccount.ID
};
var category = _daoFactory.GetListItemDao().GetByID(categoryId);
if (category == null) throw new ArgumentException();
var item = _daoFactory.GetRelationshipEventDao().CreateItem(relationshipEvent);
notifyUserList = notifyUserList != null ? notifyUserList.ToList() : new List();
var needNotify = notifyUserList.Any();
var fileListInfoHashtable = new Hashtable();
if (fileId != null)
{
var fileIds = fileId.ToList();
var files = _filesDaoFactory.GetFileDao().GetFiles(fileIds.ToArray());
if (needNotify)
{
foreach (var file in files)
{
var extension = Path.GetExtension(file.Title);
if (extension == null) continue;
var fileInfo = string.Format("{0} ({1})", file.Title, extension.ToUpper());
if (!fileListInfoHashtable.ContainsKey(fileInfo))
{
fileListInfoHashtable.Add(fileInfo, file.DownloadUrl);
}
else
{
fileInfo = string.Format("{0} ({1}, {2})", file.Title, extension.ToUpper(), file.UniqID);
fileListInfoHashtable.Add(fileInfo, file.DownloadUrl);
}
}
}
_daoFactory.GetRelationshipEventDao().AttachFiles(item.ID, fileIds.ToArray());
if (files.Any())
{
var fileAttachAction = GetFilesAttachAction(entityTypeObj, contactId);
_messageService.Send(fileAttachAction, _messageTarget.Create(item.ID), entityTitle, files.Select(x => x.Title));
}
}
if (needNotify)
{
_notifyClient.SendAboutAddRelationshipEventAdd(item, fileListInfoHashtable, _daoFactory, notifyUserList.ToArray());
}
var historyCreatedAction = GetHistoryCreatedAction(entityTypeObj, contactId);
_messageService.Send(historyCreatedAction, _messageTarget.Create(item.ID), entityTitle, category.Title);
return _mapper.Map(item);
}
///
/// Associates the selected file(s) with the entity with the ID or type specified in the request
///
///
/// Associate file with entity
///
/// Entity type
/// Entity ID
/// List of IDs of the files
/// Files
/// Entity with the file attached
[Create(@"{entityType:regex(contact|opportunity|case)}/{entityid:int}/files")]
public RelationshipEventDto AttachFiles(
[FromRoute] string entityType,
[FromRoute] int entityid,
[FromBody] IEnumerable fileids)
{
if (entityid <= 0 || fileids == null) throw new ArgumentException();
var files = _filesDaoFactory.GetFileDao().GetFiles(fileids.ToArray());
var folderid = GetRootFolderID();
if (files.Exists(file => file.FolderID.ToString() != folderid.ToString()))
throw new ArgumentException("invalid file folder");
var entityTypeObj = ToEntityType(entityType);
DomainObject entityObj;
var entityTitle = GetEntityTitle(entityTypeObj, entityid, true, out entityObj);
switch (entityTypeObj)
{
case EntityType.Contact:
var relationshipEvent1 = _daoFactory.GetRelationshipEventDao().AttachFiles(entityid, EntityType.Any, 0, fileids.ToArray());
var messageAction = entityObj is Company ? MessageAction.CompanyAttachedFiles : MessageAction.PersonAttachedFiles;
_messageService.Send(messageAction, _messageTarget.Create(entityid), entityTitle, files.Select(x => x.Title));
return _mapper.Map(relationshipEvent1);
case EntityType.Opportunity:
var relationshipEvent2 = _daoFactory.GetRelationshipEventDao().AttachFiles(0, entityTypeObj, entityid, fileids.ToArray());
_messageService.Send(MessageAction.OpportunityAttachedFiles, _messageTarget.Create(entityid), entityTitle, files.Select(x => x.Title));
return _mapper.Map(relationshipEvent2);
case EntityType.Case:
var relationshipEvent3 = _daoFactory.GetRelationshipEventDao().AttachFiles(0, entityTypeObj, entityid, fileids.ToArray());
_messageService.Send(MessageAction.CaseAttachedFiles, _messageTarget.Create(entityid), entityTitle, files.Select(x => x.Title));
return _mapper.Map(relationshipEvent3);
default:
throw new ArgumentException();
}
}
///
/// Returns the ID for the root folder used to store the files for the CRM module
///
/// Get root folder ID
/// Files
///
/// Root folder ID
///
[Read(@"files/root")]
public int GetRootFolderID()
{
return _daoFactory.GetFileDao().GetRoot();
}
///
/// Returns the list of all files for the entity with the ID or type specified in the request
///
/// Entity type
/// Entity ID
/// Get file list
/// Files
///
/// File list
///
[Read(@"{entityType:regex(contact|opportunity|case)}/{entityid:int}/files")]
public IEnumerable> GetFiles(string entityType, int entityid)
{
if (entityid <= 0) throw new ArgumentException();
var entityTypeObj = ToEntityType(entityType);
switch (entityTypeObj)
{
case EntityType.Contact:
return _daoFactory.GetRelationshipEventDao().GetAllFiles(new[] { entityid }, EntityType.Any, 0).ConvertAll(file => _fileWrapperHelper.Get(file));
case EntityType.Opportunity:
case EntityType.Case:
return _daoFactory.GetRelationshipEventDao().GetAllFiles(null, entityTypeObj, entityid).ConvertAll(file => _fileWrapperHelper.Get(file));
default:
throw new ArgumentException();
}
}
///
/// Deletes the file with the ID specified in the request
///
/// Delete file
/// Files
/// File ID
///
///
///
/// File Info
///
[Delete(@"files/{fileid:int}")]
public FileWrapper DeleteCRMFile(int fileid)
{
if (fileid < 0) throw new ArgumentException();
var file = _filesDaoFactory.GetFileDao().GetFile(fileid);
if (file == null) throw new ItemNotFoundException();
var result = _fileWrapperHelper.Get(file);
var _eventsDao = _daoFactory.GetRelationshipEventDao();
var eventIDs = _eventsDao.RemoveFile(file);
var events = new List();
eventIDs.ForEach(id => events.Add(_eventsDao.GetByID(id)));
foreach (var evt in events)
{
DomainObject entityObj;
var entityTitle = evt.ContactID > 0
? GetEntityTitle(EntityType.Contact, evt.ContactID, false, out entityObj)
: GetEntityTitle(evt.EntityType, evt.EntityID, false, out entityObj);
var messageAction = GetFilesDetachAction(evt.EntityType, evt.ContactID);
_messageService.Send(messageAction, _messageTarget.Create(file.ID), entityTitle, file.Title);
}
return result;
}
private IEnumerable ToListRelationshipEventDto(List itemList)
{
if (itemList.Count == 0) return new List();
var result = new List();
var contactIDs = new List();
var eventIDs = new List();
var categoryIDs = new List();
var entityDtosIDs = new Dictionary>();
foreach (var item in itemList)
{
eventIDs.Add(item.ID);
if (!categoryIDs.Contains(item.CategoryID))
{
categoryIDs.Add(item.CategoryID);
}
if (item.ContactID > 0 && !contactIDs.Contains(item.ContactID))
{
contactIDs.Add(item.ContactID);
}
if (item.EntityID <= 0) continue;
if (!entityDtosIDs.ContainsKey(item.EntityType))
{
entityDtosIDs.Add(item.EntityType, new List
{
item.EntityID
});
}
else if (!entityDtosIDs[item.EntityType].Contains(item.EntityID))
{
entityDtosIDs[item.EntityType].Add(item.EntityID);
}
}
var entityDtos = new Dictionary();
foreach (var entityType in entityDtosIDs.Keys)
{
switch (entityType)
{
case EntityType.Opportunity:
_daoFactory.GetDealDao().GetDeals(entityDtosIDs[entityType].Distinct().ToArray())
.ForEach(item =>
{
if (item == null) return;
entityDtos.Add(
string.Format("{0}_{1}", (int)entityType, item.ID),
new EntityDto
{
EntityId = item.ID,
EntityTitle = item.Title,
EntityType = "opportunity"
});
});
break;
case EntityType.Case:
_daoFactory.GetCasesDao().GetByID(entityDtosIDs[entityType].ToArray())
.ForEach(item =>
{
if (item == null) return;
entityDtos.Add(
string.Format("{0}_{1}", (int)entityType, item.ID),
new EntityDto
{
EntityId = item.ID,
EntityTitle = item.Title,
EntityType = "case"
});
});
break;
default:
throw new ArgumentException();
}
}
var categories = _daoFactory.GetListItemDao().GetItems(categoryIDs.ToArray()).ToDictionary(x => x.ID, x => _mapper.Map(x));
var files = _daoFactory.GetRelationshipEventDao().GetFiles(eventIDs.ToArray());
var contacts = _daoFactory.GetContactDao().GetContacts(contactIDs.ToArray()).ToDictionary(item => item.ID, x => _mapper.Map(x));
foreach (var item in itemList)
{
var eventObjWrap = _mapper.Map(item);
if (contacts.ContainsKey(item.ContactID))
{
eventObjWrap.Contact = contacts[item.ContactID];
}
if (item.EntityID > 0)
{
var entityStrKey = string.Format("{0}_{1}", (int)item.EntityType, item.EntityID);
if (entityDtos.ContainsKey(entityStrKey))
{
eventObjWrap.Entity = entityDtos[entityStrKey];
}
}
eventObjWrap.Files = files.ContainsKey(item.ID) ? files[item.ID].ConvertAll(file =>
_fileWrapperHelper.Get(file)) : new List>();
if (categories.ContainsKey(item.CategoryID))
{
eventObjWrap.Category = categories[item.CategoryID];
}
result.Add(eventObjWrap);
}
return result;
}
private MessageAction GetHistoryCreatedAction(EntityType entityType, int contactId)
{
if (contactId > 0)
{
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyCreatedHistoryEvent : MessageAction.PersonCreatedHistoryEvent;
}
switch (entityType)
{
case EntityType.Opportunity:
return MessageAction.OpportunityCreatedHistoryEvent;
case EntityType.Case:
return MessageAction.CaseCreatedHistoryEvent;
case EntityType.Any:
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyCreatedHistoryEvent : MessageAction.PersonCreatedHistoryEvent;
default:
throw new ArgumentException("Invalid entityType: " + entityType);
}
}
private MessageAction GetHistoryDeletedAction(EntityType entityType, int contactId)
{
if (contactId > 0)
{
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyDeletedHistoryEvent : MessageAction.PersonDeletedHistoryEvent;
}
switch (entityType)
{
case EntityType.Opportunity:
return MessageAction.OpportunityDeletedHistoryEvent;
case EntityType.Case:
return MessageAction.CaseDeletedHistoryEvent;
case EntityType.Any:
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyDeletedHistoryEvent : MessageAction.PersonDeletedHistoryEvent;
default:
throw new ArgumentException("Invalid entityType: " + entityType);
}
}
private MessageAction GetFilesAttachAction(EntityType entityType, int contactId)
{
if (contactId > 0)
{
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyAttachedFiles : MessageAction.PersonAttachedFiles;
}
switch (entityType)
{
case EntityType.Opportunity:
return MessageAction.OpportunityAttachedFiles;
case EntityType.Case:
return MessageAction.CaseAttachedFiles;
case EntityType.Any:
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyAttachedFiles : MessageAction.PersonAttachedFiles;
default:
throw new ArgumentException("Invalid entityType: " + entityType);
}
}
private MessageAction GetFilesDetachAction(EntityType entityType, int contactId)
{
if (contactId > 0)
{
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyDetachedFile : MessageAction.PersonDetachedFile;
}
switch (entityType)
{
case EntityType.Opportunity:
return MessageAction.OpportunityDetachedFile;
case EntityType.Case:
return MessageAction.CaseDetachedFile;
case EntityType.Any:
var contact = _daoFactory.GetContactDao().GetByID(contactId);
return contact is Company ? MessageAction.CompanyDetachedFile : MessageAction.PersonAttachedFiles;
default:
throw new ArgumentException("Invalid entityType: " + entityType);
}
}
}
}