diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj
index 0e311e84..8112b623 100644
--- a/Disco.Models/Disco.Models.csproj
+++ b/Disco.Models/Disco.Models.csproj
@@ -146,6 +146,8 @@
+
+
diff --git a/Disco.Models/Services/Messaging/Email.cs b/Disco.Models/Services/Messaging/Email.cs
index a7ba8ad0..e12ad07c 100644
--- a/Disco.Models/Services/Messaging/Email.cs
+++ b/Disco.Models/Services/Messaging/Email.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Disco.Models.Services.Messaging
{
@@ -27,5 +29,10 @@ namespace Disco.Models.Services.Messaging
Subject = subject;
Body = body;
}
+
+ public static IEnumerable ParseEmailAddresses(string emailAddresses)
+ {
+ return emailAddresses.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
+ }
}
}
diff --git a/Disco.Models/Services/Users/Contact/UserContact.cs b/Disco.Models/Services/Users/Contact/UserContact.cs
new file mode 100644
index 00000000..f773f2c8
--- /dev/null
+++ b/Disco.Models/Services/Users/Contact/UserContact.cs
@@ -0,0 +1,161 @@
+using Disco.Models.Repository;
+using System;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Disco.Models.Services.Users.Contact
+{
+ public abstract class UserContact
+ {
+ public User User { get; }
+ public UserContactType ContactType { get; }
+ public string Source { get; }
+ public string Name { get; }
+
+ public abstract string Value { get; }
+
+ public UserContact(User user, UserContactType contactType, string source, string name)
+ {
+ User = user;
+ ContactType = contactType;
+ Source = source;
+ Name = name;
+ }
+
+ protected static bool TryParse(User user, string source, Regex validator, string value, Func generator, out T instance) where T : UserContact
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ instance = null;
+ return false;
+ }
+
+ var match = validator.Match(value);
+
+ if (!match.Success)
+ {
+ instance = null;
+ return false;
+ }
+
+ var result = match.Value;
+ var name = default(string);
+ if (match.Index > 0)
+ {
+ name = value.Substring(0, match.Index).Trim();
+ if (name.Length > 0)
+ {
+ switch (name.Last())
+ {
+ case '<':
+ case '[':
+ case '(':
+ case '{':
+ case '-':
+ name = name.Substring(0, name.Length - 1).Trim();
+ break;
+ }
+ }
+ if (name.Length == 0)
+ name = default;
+ }
+
+ instance = generator(user, source, name, result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ if (!string.IsNullOrWhiteSpace(Name))
+ {
+ return $"{Name} <{Value}>";
+ }
+ else
+ {
+ return Value;
+ }
+ }
+ }
+
+ public sealed class UserContactEmail : UserContact
+ {
+ private static Regex validator = new Regex(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ public string EmailAddress { get; }
+ public override string Value => EmailAddress;
+
+ public UserContactEmail(User user, string source, string name, string emailAddress)
+ : base(user, UserContactType.Email, source, name)
+ {
+ if (string.IsNullOrWhiteSpace(emailAddress))
+ throw new ArgumentNullException(nameof(emailAddress));
+
+ EmailAddress = emailAddress;
+ }
+
+ public static bool TryParse(User user, string source, string value, out UserContactEmail contact) =>
+ TryParse(user, source, validator, value,
+ (u, s, name, emailAddress) => new UserContactEmail(u, s, name, emailAddress),
+ out contact);
+ }
+
+ public sealed class UserContactAustralianPhone : UserContact
+ {
+ private static Regex validator = new Regex(@"(?:\+?61\s*[0-9][ \-\.]*?|0[0-9][ \-\.]*?|[(\[]\s*0[0-9]\s*[)\]])\s*(?:[0-9][ \-\.]*?){8}(?=\s*[>\]})\-]?\s*$)", RegexOptions.Compiled);
+
+ public string PhoneNumber { get; }
+ public override string Value => PhoneNumber;
+
+ public UserContactAustralianPhone(User user, string source, string name, string phoneNumber)
+ : base(user, UserContactType.Phone, source, name)
+ {
+ if (string.IsNullOrWhiteSpace(phoneNumber))
+ throw new ArgumentNullException(nameof(phoneNumber));
+
+ PhoneNumber = phoneNumber;
+ }
+
+ public static bool TryParse(User user, string source, string value, out UserContactAustralianPhone contact) =>
+ TryParse(user, source, validator, value,
+ (u, s, name, phoneNumber) => new UserContactAustralianPhone(u, s, name, phoneNumber),
+ out contact);
+
+ public override string ToString()
+ {
+ if (!string.IsNullOrWhiteSpace(Name))
+ return $"{Name} <{PhoneNumber}>";
+ else
+ return PhoneNumber;
+ }
+ }
+
+ public sealed class UserContactAustralianMobile : UserContact
+ {
+ private static Regex validator = new Regex(@"(?:\+?61\s*4[ \-\.]*?|04[ \-\.]*?|[(\[]\s*04\s*[)\]])\s*(?:[0-9][ \-\.]*?){8}(?=\s*[>\]})\-]?\s*$)", RegexOptions.Compiled);
+
+ public string PhoneNumber { get; }
+ public override string Value => PhoneNumber;
+
+ public UserContactAustralianMobile(User user, string source, string name, string phoneNumber)
+ : base(user, UserContactType.MobilePhone, source, name)
+ {
+ if (string.IsNullOrWhiteSpace(phoneNumber))
+ throw new ArgumentNullException(nameof(phoneNumber));
+
+ PhoneNumber = phoneNumber;
+ }
+
+ public static bool TryParse(User user, string source, string value, out UserContactAustralianPhone contact) =>
+ TryParse(user, source, validator, value,
+ (u, s, name, phoneNumber) => new UserContactAustralianPhone(u, s, name, phoneNumber),
+ out contact);
+
+ public override string ToString()
+ {
+ if (!string.IsNullOrWhiteSpace(Name))
+ return $"{Name} <{PhoneNumber}>";
+ else
+ return PhoneNumber;
+ }
+ }
+}
diff --git a/Disco.Models/Services/Users/Contact/UserContactType.cs b/Disco.Models/Services/Users/Contact/UserContactType.cs
new file mode 100644
index 00000000..965a1b2f
--- /dev/null
+++ b/Disco.Models/Services/Users/Contact/UserContactType.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Disco.Models.Services.Users.Contact
+{
+ [Flags]
+ public enum UserContactType
+ {
+ Email = 1,
+ MobilePhone,
+ Phone,
+ AddressMail,
+ AddressHome,
+ }
+}
diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj
index 3d978e29..35313cca 100644
--- a/Disco.Services/Disco.Services.csproj
+++ b/Disco.Services/Disco.Services.csproj
@@ -396,6 +396,7 @@
+
@@ -456,6 +457,7 @@
+
diff --git a/Disco.Services/Plugins/Features/DetailsProvider/UserContactFeature.cs b/Disco.Services/Plugins/Features/DetailsProvider/UserContactFeature.cs
new file mode 100644
index 00000000..68fe42a2
--- /dev/null
+++ b/Disco.Services/Plugins/Features/DetailsProvider/UserContactFeature.cs
@@ -0,0 +1,13 @@
+using Disco.Data.Repository;
+using Disco.Models.Repository;
+using Disco.Models.Services.Users.Contact;
+using System.Collections.Generic;
+
+namespace Disco.Services.Plugins.Features.DetailsProvider
+{
+ [PluginFeatureCategory(DisplayName = "User Contact Providers")]
+ public abstract class UserContactFeature : PluginFeature
+ {
+ public abstract IEnumerable GetContacts(DiscoDataContext database, User user, UserContactType? contactType = null);
+ }
+}
diff --git a/Disco.Services/Users/Contact/UserContactService.cs b/Disco.Services/Users/Contact/UserContactService.cs
new file mode 100644
index 00000000..06b1ec4e
--- /dev/null
+++ b/Disco.Services/Users/Contact/UserContactService.cs
@@ -0,0 +1,104 @@
+using Disco.Data.Repository;
+using Disco.Models.Repository;
+using Disco.Models.Services.Users.Contact;
+using Disco.Services.Plugins.Features.DetailsProvider;
+using System.Collections.Generic;
+using System.Linq;
+using ZXing;
+
+namespace Disco.Services.Users.Contact
+{
+ public static class UserContactService
+ {
+ public static List GetContacts(DiscoDataContext database, User user)
+ => GetContacts(database, user, null);
+
+ public static List GetContacts(DiscoDataContext database, User user, UserContactType? contactType = null)
+ {
+ var contacts = new List();
+
+ if (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.Email))
+ {
+ if (!string.IsNullOrWhiteSpace(user.EmailAddress) &&
+ UserContactEmail.TryParse(user, "Active Directory", $"{user.DisplayName} <{user.EmailAddress}>", out var contact))
+ {
+ contacts.Add(contact);
+ }
+ }
+
+ var foundMobilePhone = false;
+ if (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.MobilePhone))
+ {
+ if (!string.IsNullOrWhiteSpace(user.PhoneNumber) &&
+ UserContactAustralianMobile.TryParse(user, "Active Directory", $"{user.DisplayName} <{user.PhoneNumber}>", out var contact))
+ {
+ contacts.Add(contact);
+ foundMobilePhone = true;
+ }
+ }
+
+ if (!foundMobilePhone && (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.Phone)))
+ {
+ if (!string.IsNullOrWhiteSpace(user.PhoneNumber) &&
+ UserContactAustralianPhone.TryParse(user, "Active Directory", $"{user.DisplayName} <{user.PhoneNumber}>", out var contact))
+ {
+ contacts.Add(contact);
+ }
+ }
+
+ // from plugin feature
+ var features = Plugins.Plugins.GetPluginFeatures(typeof(UserContactFeature));
+ foreach (var feature in features)
+ {
+ var instance = feature.CreateInstance();
+ contacts.AddRange(instance.GetContacts(database, user, contactType));
+ }
+
+ // from user details
+ contacts.AddRange(GetContactsFromUserDetails(database, user, contactType));
+
+ return contacts;
+ }
+
+ public static IEnumerable GetContactsFromUserDetails(DiscoDataContext database, User user, UserContactType? contactType = null)
+ {
+ var service = new DetailsProviderService(database);
+
+ user = database.Users.First(u => u.UserId == user.UserId);
+ var details = service.GetDetails(user);
+
+ if ((details?.Details?.Count ?? 0) == 0)
+ yield break;
+
+ foreach (var item in details.Details)
+ {
+ if (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.Email))
+ {
+ if (UserContactEmail.TryParse(user, item.Key, item.Value, out var contact))
+ {
+ yield return contact;
+ continue;
+ }
+ }
+
+ if (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.MobilePhone))
+ {
+ if (UserContactAustralianMobile.TryParse(user, item.Key, item.Value, out var contact))
+ {
+ yield return contact;
+ continue;
+ }
+ }
+
+ if (!contactType.HasValue || contactType.Value.HasFlag(UserContactType.Phone))
+ {
+ if (UserContactAustralianPhone.TryParse(user, item.Key, item.Value, out var contact))
+ {
+ yield return contact;
+ continue;
+ }
+ }
+ }
+ }
+ }
+}