/* * * (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 ASC.Api.Core; using ASC.CRM.ApiModels; using ASC.Api.Documents; using ASC.Common.Web; using ASC.CRM.Core; using ASC.CRM.Core.Entities; using ASC.CRM.Core.Enums; using ASC.MessagingSystem; using ASC.Web.Api.Routing; using ASC.Web.Files.Utils; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using OrderBy = ASC.CRM.Core.Entities.OrderBy; using ASC.Api.CRM; using ASC.CRM.Core.Dao; using ASC.Web.CRM.Services.NotifyService; using ASC.Core; using ASC.Web.Core.Users; namespace ASC.CRM.Api { public class RelationshipEventsController : BaseApiController { public RelationshipEventsController(CRMSecurity cRMSecurity, DaoFactory daoFactory, ApiContext apiContext, MessageTarget messageTarget, MessageService messageService, ContactDtoHelper contactBaseDtoHelper, RelationshipEventDtoHelper relationshipEventDtoHelper, FileWrapperHelper fileWrapperHelper, ASC.Files.Core.Data.DaoFactory filesDaoFactory, FileUploader fileUploader, HistoryCategoryDtoHelper historyCategoryDtoHelper) : base(daoFactory, cRMSecurity) { ApiContext = apiContext; MessageTarget = messageTarget; MessageService = messageService; ContactDtoHelper = contactBaseDtoHelper; RelationshipEventDtoHelper = relationshipEventDtoHelper; FileWrapperHelper = fileWrapperHelper; FilesDaoFactory = filesDaoFactory; FileUploader = fileUploader; HistoryCategoryDtoHelper = historyCategoryDtoHelper; } public HistoryCategoryDtoHelper HistoryCategoryDtoHelper { get; } public FileUploader FileUploader { get; } public ASC.Files.Core.Data.DaoFactory FilesDaoFactory { get; set; } public FileWrapperHelper FileWrapperHelper { get; } public RelationshipEventDtoHelper RelationshipEventDtoHelper { get; } public ContactDtoHelper ContactDtoHelper { get; } private ApiContext ApiContext { get; } public MessageService MessageService { get; } public MessageTarget MessageTarget { get; } /// /// 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 = RelationshipEventDtoHelper.Get(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(string entityType, int entityid, string title, string 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( // string entityType, // int entityid, // Stream file, // ContentType contentType, // ContentDisposition contentDisposition, // IEnumerable files, // bool storeOriginalFileFlag // ) //{ // FilesSettings.StoreOriginalFilesSetting = 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( // string entityType, // int entityId, // int contactId, // string content, // int categoryId, // ApiDateTime created, // IEnumerable fileId, // IEnumerable 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 wrapper = RelationshipEventDtoHelper.Get(item); // var historyCreatedAction = GetHistoryCreatedAction(entityTypeObj, contactId); // MessageService.Send( historyCreatedAction, MessageTarget.Create(item.ID), entityTitle, category.Title); // return wrapper; //} /// /// 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(string entityType, int entityid, 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 RelationshipEventDtoHelper.Get(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 RelationshipEventDtoHelper.Get(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 RelationshipEventDtoHelper.Get(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 => HistoryCategoryDtoHelper.Get(x)); var files = DaoFactory.GetRelationshipEventDao().GetFiles(eventIDs.ToArray()); var contacts = DaoFactory.GetContactDao().GetContacts(contactIDs.ToArray()).ToDictionary(item => item.ID, x => ContactDtoHelper.GetContactBaseDto(x)); foreach (var item in itemList) { var eventObjWrap = RelationshipEventDtoHelper.Get(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); } } } }