From d21eb821a4fcedf389ca91595d44c754670b4c11 Mon Sep 17 00:00:00 2001 From: Patrick Simpson Date: Wed, 7 Jun 2017 13:18:40 +0200 Subject: [PATCH] [KOE-114] Added change suppression for private events in shared folders. --- .../AcaciaZPushPlugin.csproj | 4 + .../Features/GAB/FeatureGAB.cs | 6 +- .../SharedFolders/FeatureSharedFolders.cs | 106 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 18 +++ .../Properties/Resources.resx | 6 + .../AcaciaZPushPlugin/Stubs/Enums.cs | 9 ++ .../AcaciaZPushPlugin/Stubs/IAddIn.cs | 1 + .../Stubs/IAppointmentItem.cs | 2 + .../AcaciaZPushPlugin/Stubs/IExplorer.cs | 11 +- .../AcaciaZPushPlugin/Stubs/IInspector.cs | 47 ++++++++ .../AcaciaZPushPlugin/Stubs/IInspectors.cs | 12 ++ .../AcaciaZPushPlugin/Stubs/IOutlookWindow.cs | 6 + .../Stubs/OutlookWrappers/AddInWrapper.cs | 5 + .../OutlookWrappers/AppointmentItemWrapper.cs | 5 + .../Stubs/OutlookWrappers/ExplorerWrapper.cs | 5 + .../Stubs/OutlookWrappers/InspectorWrapper.cs | 48 ++++++++ .../OutlookWrappers/InspectorsWrapper.cs | 30 +++++ 17 files changed, 312 insertions(+), 9 deletions(-) create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspector.cs create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspectors.cs create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorWrapper.cs create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorsWrapper.cs diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj index ea3dcfe..51e22ce 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj @@ -325,6 +325,8 @@ + + @@ -344,6 +346,7 @@ + @@ -354,6 +357,7 @@ + diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs index 6c1471b..800d996 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs @@ -330,9 +330,9 @@ namespace Acacia.Features.GAB // Show message and cancel event // TODO: show on active inspector if used // [KOE-108] If the window is not specified as parent, it sometimes doesn't show - var res = MessageBox.Show(ThisAddIn.Instance.Window, - StringUtil.GetResourceString("GABEvent_Body"), - StringUtil.GetResourceString("GABEvent_Title"), + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.GABEvent_Body, + Properties.Resources.GABEvent_Title, MessageBoxButtons.OK, MessageBoxIcon.Warning ); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs index a0d6c11..fe20d52 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Forms; using static Acacia.DebugOptions; namespace Acacia.Features.SharedFolders @@ -67,6 +68,9 @@ namespace Acacia.Features.SharedFolders // Sync state Watcher.Sync.AddTask(this, Name, AdditionalFolders_Sync); + + // Private shared appointment + SetupPrivateAppointmentSuppression(); } #region UI @@ -158,7 +162,109 @@ namespace Acacia.Features.SharedFolders return share; } + public static bool IsSharedFolder(IFolder folder) + { + string id = (string)folder.GetProperty(OutlookConstants.PR_ZPUSH_FOLDER_ID); + return id?.StartsWith("S") == true; + } + #endregion + #region Private appointments + + [AcaciaOption("If enabled, modifications to private appointments in shared calendars are suppressed. " + + "This should only be disabled for debug purposes.")] + public bool SuppressModificationsPrivateAppointments + { + get { return GetOption(OPTION_SUPPRESS_MODIFICATIONS_PRIVATE_APPOINTMENTS); } + set { SetOption(OPTION_SUPPRESS_MODIFICATIONS_PRIVATE_APPOINTMENTS, value); } + } + private static readonly BoolOption OPTION_SUPPRESS_MODIFICATIONS_PRIVATE_APPOINTMENTS = + new BoolOption("SuppressModificationsPrivateAppointments", true); + + // TODO: this is largely duplicated from FeatureGAB, separate into helper + private void SetupPrivateAppointmentSuppression() + { + if (SuppressModificationsPrivateAppointments && MailEvents != null) + { + MailEvents.BeforeDelete += SuppressEventHandler_Delete; + MailEvents.PropertyChange += SuppressEventHandler_PropertyChange; + MailEvents.Write += SuppressEventHandler_Write; + } + } + + + private void SuppressEventHandler_Delete(IItem item, ref bool cancel) + { + SuppressEventHandler(item, false, ref cancel); + } + + /// + /// When copying a contact from the GAB to a local folder, Outlook raises the Write event on + /// the original. To detect this, we set this id on every property change (which is signalled before + /// write), and only suppress if anything has actually changed. If we suppress, the flag is cleared again. + /// + private string _propertyChangeId; + + private void SuppressEventHandler_PropertyChange(IItem item, string propertyName) + { + if (_propertyChangeId == item.EntryID) + return; + _propertyChangeId = item.EntryID; + } + + private void SuppressEventHandler_Write(IItem item, ref bool cancel) + { + if (_propertyChangeId == item.EntryID) + { + SuppressEventHandler(item, true, ref cancel); + _propertyChangeId = null; + } + } + + private void SuppressEventHandler(IItem item, bool findInspector, ref bool cancel) + { + // Check if it's an appointment + IAppointmentItem appointment = item as IAppointmentItem; + if (appointment == null) + return; + + // Check if it's private. Confidential is also considered private + if (appointment.Sensitivity < Sensitivity.Private) + return; + + // Check if in a shared folder + using (IFolder parent = item.Parent) + { + if (parent == null || !IsSharedFolder(parent)) + return; + } + + if (findInspector) + { + // Find and close any inspector + foreach (IInspector inspector in ThisAddIn.Instance.GetInspectors()) + { + using (IItem inspectItem = inspector.GetCurrentItem()) + { + if (appointment.EntryID == inspectItem.EntryID) + { + inspector.Close(InspectorClose.Discard); + } + } + } + } + + // Private appointment in a shared folder, suppress + cancel = true; + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.SharedFolders_PrivateEvent_Body, + Properties.Resources.SharedFolders_PrivateEvent_Title, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + + #endregion } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs index dd440d8..2187695 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs @@ -1111,6 +1111,24 @@ namespace Acacia.Properties { } } + /// + /// Looks up a localized string similar to Changing other people's private events is not allowed.. + /// + internal static string SharedFolders_PrivateEvent_Body { + get { + return ResourceManager.GetString("SharedFolders_PrivateEvent_Body", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Private event. + /// + internal static string SharedFolders_PrivateEvent_Title { + get { + return ResourceManager.GetString("SharedFolders_PrivateEvent_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Public folders. /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx index b519732..6e77c5c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx @@ -508,4 +508,10 @@ Synchronisation stalled - {0} {0} will be replaced with the account name + + Changing other people's private events is not allowed. + + + Private event + \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs index deadd05..5dcf084 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs @@ -84,4 +84,13 @@ namespace Acacia.Stubs CC = 2, BCC = 3 } + + // Replacement for OlSensitivity + public enum Sensitivity + { + Normal, + Personal, + Private, + Confidential + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs index 473da0c..f88b778 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs @@ -42,6 +42,7 @@ namespace Acacia.Stubs OutlookUI OutlookUI { get; } ISystemWindow Window { get; } IExplorer GetActiveExplorer(); + IInspectors GetInspectors(); #endregion diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAppointmentItem.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAppointmentItem.cs index 3f088da..8b069eb 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAppointmentItem.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAppointmentItem.cs @@ -31,5 +31,7 @@ namespace Acacia.Stubs DateTime End { get; set; } string Location { get; set; } string GlobalAppointmentId { get; } + + Sensitivity Sensitivity { get; } } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IExplorer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IExplorer.cs index 45424dc..1e88ce7 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IExplorer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IExplorer.cs @@ -25,18 +25,17 @@ namespace Acacia.Stubs { public interface IExplorer : IOutlookWindow { - /// - /// Returns the command bars. - /// - /// The command bars. The caller is responsible for disposing. - ICommandBars GetCommandBars(); - /// /// Returns the currently selected folder, or null if no folder is selected. /// /// The folder. The caller is responsible for disposing. IFolder GetCurrentFolder(); + /// + /// Closes the explorer window. + /// + void Close(); + #region Events // TODO: custom delegates event NSOutlookDelegates.ExplorerEvents_10_SelectionChangeEventHandler SelectionChange; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspector.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspector.cs new file mode 100644 index 0000000..8b45327 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspector.cs @@ -0,0 +1,47 @@ +/// Copyright 2017 Kopano b.v. +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License, version 3, +/// as published by the Free Software Foundation. +/// +/// This program 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 Affero General Public License for more details. +/// +/// You should have received a copy of the GNU Affero General Public License +/// along with this program.If not, see. +/// +/// Consult LICENSE file for details + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NSOutlookDelegates = Microsoft.Office.Interop.Outlook; + +namespace Acacia.Stubs +{ + public enum InspectorClose + { + Save, + Discard, + PromptForSave + } + + public interface IInspector : IOutlookWindow + { + + /// + /// Closes the inspector window. + /// + void Close(InspectorClose mode); + + /// + /// Returns the currently selected item, or null if no item is selected. + /// + /// The item. The caller is responsible for disposing. + IItem GetCurrentItem(); + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspectors.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspectors.cs new file mode 100644 index 0000000..2c0ae86 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IInspectors.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Stubs +{ + public interface IInspectors : IEnumerable + { + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IOutlookWindow.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IOutlookWindow.cs index 6ba89e5..44e2205 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IOutlookWindow.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IOutlookWindow.cs @@ -24,5 +24,11 @@ namespace Acacia.Stubs { public interface IOutlookWindow : IComWrapper { + /// + /// Returns the command bars. + /// + /// The command bars. The caller is responsible for disposing. + ICommandBars GetCommandBars(); + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs index 33b2d01..74d51d9 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs @@ -254,6 +254,11 @@ namespace Acacia.Stubs.OutlookWrappers return new ExplorerWrapper(_app.ActiveExplorer()); } + public IInspectors GetInspectors() + { + return new InspectorsWrapper(_app.Inspectors); + } + #region Window handle /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs index a866794..8a77c90 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs @@ -57,6 +57,11 @@ namespace Acacia.Stubs.OutlookWrappers get { return _item.GlobalAppointmentID; } } + public Sensitivity Sensitivity + { + get { return (Sensitivity)_item.Sensitivity; } + } + #endregion #region Wrapper methods diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ExplorerWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ExplorerWrapper.cs index e400a28..02f05f7 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ExplorerWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ExplorerWrapper.cs @@ -30,6 +30,11 @@ namespace Acacia.Stubs.OutlookWrappers { } + public void Close() + { + _item.Close(); + } + public event NSOutlook.ExplorerEvents_10_SelectionChangeEventHandler SelectionChange { add { _item.SelectionChange += value; } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorWrapper.cs new file mode 100644 index 0000000..6e907f8 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorWrapper.cs @@ -0,0 +1,48 @@ +/// Copyright 2017 Kopano b.v. +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License, version 3, +/// as published by the Free Software Foundation. +/// +/// This program 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 Affero General Public License for more details. +/// +/// You should have received a copy of the GNU Affero General Public License +/// along with this program.If not, see. +/// +/// Consult LICENSE file for details + +using Acacia.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NSOutlook = Microsoft.Office.Interop.Outlook; + +namespace Acacia.Stubs.OutlookWrappers +{ + class InspectorWrapper : ComWrapper, IInspector + { + public InspectorWrapper(NSOutlook.Inspector item) : base(item) + { + } + + public void Close(InspectorClose mode) + { + _item.Close((NSOutlook.OlInspectorClose)mode); + } + + public ICommandBars GetCommandBars() + { + return new CommandBarsWrapper(_item.CommandBars); + } + + public IItem GetCurrentItem() + { + return Mapping.Wrap(_item.CurrentItem); + } + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorsWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorsWrapper.cs new file mode 100644 index 0000000..a808c75 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/InspectorsWrapper.cs @@ -0,0 +1,30 @@ +using Acacia.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NSOutlook = Microsoft.Office.Interop.Outlook; +using System.Collections; + +namespace Acacia.Stubs.OutlookWrappers +{ + class InspectorsWrapper: ComWrapper, IInspectors + { + public InspectorsWrapper(NSOutlook.Inspectors item) : base(item) + { + } + + public IEnumerator GetEnumerator() + { + foreach (NSOutlook.Inspector inspector in _item) + yield return new InspectorWrapper(inspector); + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (NSOutlook.Inspector inspector in _item) + yield return new InspectorWrapper(inspector); + } + } +}