DigitalForm.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Newtonsoft.Json.Linq;
  6. using TextFieldParserStandard;
  7. namespace InABox.Core
  8. {
  9. [UserTracking("Digital Forms")]
  10. public class DigitalForm : Entity, IRemotable, IPersistent, ILicense<DigitalFormsLicense>, IDuplicatable
  11. {
  12. /// <summary>
  13. /// The following functions support PNG, BMP and JPEG
  14. /// </summary>
  15. /// <param name="data"></param>
  16. /// <returns></returns>
  17. private static readonly byte[] bmpHeader = Encoding.ASCII.GetBytes("BM");
  18. private static readonly byte[] pngHeader = { 137, 80, 78, 71 };
  19. private static readonly byte[] jpegHeader = { 255, 216, 255, 224 };
  20. private static readonly byte[] jpeg2Header = { 255, 216, 255, 225 };
  21. [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
  22. [EditorSequence(1)]
  23. public string Code { get; set; }
  24. [TextBoxEditor]
  25. [EditorSequence(2)]
  26. public string Description { get; set; }
  27. [ComboLookupEditor(typeof(DigitalFormCategoryLookups))]
  28. [EditorSequence(3)]
  29. public string AppliesTo { get; set; }
  30. [CheckBoxEditor]
  31. [EditorSequence(4)]
  32. public bool Active { get; set; }
  33. [CheckBoxEditor]
  34. [EditorSequence(5)]
  35. public bool Secure { get; set; }
  36. [EditorSequence(6)]
  37. public DigitalFormGroupLink Group { get; set; }
  38. [NullEditor]
  39. public string Report { get; set; }
  40. public IEntityDuplicator GetDuplicator()
  41. {
  42. var result = new EntityDuplicator<DigitalForm>();
  43. result.AddChild<DigitalForm, DigitalFormVariable>(x => x.Form);
  44. result.AddChild<DigitalForm, DigitalFormLayout>(x => x.Form);
  45. return result;
  46. }
  47. protected override void Init()
  48. {
  49. base.Init();
  50. Active = true;
  51. Secure = false;
  52. Group = new DigitalFormGroupLink();
  53. }
  54. public override string ToString()
  55. {
  56. return string.Format("{0}: {1}", Code, Description);
  57. }
  58. private static bool IsValidImage(byte[] data)
  59. {
  60. return bmpHeader.SequenceEqual(data.Take(bmpHeader.Length))
  61. || pngHeader.SequenceEqual(data.Take(pngHeader.Length))
  62. || jpegHeader.SequenceEqual(data.Take(jpegHeader.Length))
  63. || jpeg2Header.SequenceEqual(data.Take(jpeg2Header.Length));
  64. }
  65. private static bool IsValidImage(string base64Data)
  66. {
  67. var firstBytes = base64Data.Substring(0, Math.Min(8, base64Data.Length));
  68. return IsValidImage(Convert.FromBase64String(firstBytes));
  69. }
  70. /// <summary>
  71. /// Takes a serialized FormData string and BlobData string and deserializes into one dictionary.
  72. /// The result of this is just as if you were deserializing formData as per usual.
  73. /// </summary>
  74. /// <param name="formData"></param>
  75. /// <param name="blobData"></param>
  76. /// <returns></returns>
  77. public static Dictionary<string, object?>? DeserializeFormData(string formData, string? blobData)
  78. {
  79. var values = Serialization.Deserialize<Dictionary<string, object?>>(formData);
  80. if (values is null)
  81. return null;
  82. if(blobData != null)
  83. {
  84. var blobs = Serialization.Deserialize<Dictionary<string, object?>>(blobData);
  85. if(blobs != null)
  86. {
  87. var updates = new List<Tuple<string, object?>>();
  88. foreach (var (key, value) in values)
  89. {
  90. if ((value is string str && blobs.TryGetValue(str, out var blob))
  91. || (value is Guid guid && blobs.TryGetValue(guid.ToString(), out blob)))
  92. {
  93. updates.Add(new Tuple<string, object?>(key, blob));
  94. }
  95. }
  96. foreach(var (key, value) in updates)
  97. {
  98. values[key] = value;
  99. }
  100. }
  101. }
  102. return values;
  103. }
  104. /// <summary>
  105. /// Like <see cref="DeserializeFormData(string, string?)"/>, but takes the FormData and BlobData from <paramref name="form"/>,
  106. /// rather than having to specify them manually.
  107. /// </summary>
  108. /// <param name="form"></param>
  109. /// <returns></returns>
  110. public static Dictionary<string, object?>? DeserializeFormData(IDigitalFormInstance form)
  111. => DeserializeFormData(form.FormData, form.BlobData);
  112. /// <summary>
  113. /// Takes a form instance, set of variables and some saved values, and serializes the FormData and BlobData into the respective
  114. /// fields of <paramref name="form"/>.
  115. /// </summary>
  116. /// <remarks>
  117. /// Doesn't return anything, but saves the serialized results directly into form.FormData and form.BlobData.
  118. /// It choose which fields should be saved into BlobData based on whether the form field has the <see cref="IDFBlobField"/> interface.
  119. /// <br/>
  120. /// <paramref name="values"/> should be the same as what you pass into <see cref="Serialization.Serialize(object?, bool)"/>.
  121. /// </remarks>
  122. /// <param name="form">The form instance.</param>
  123. /// <param name="variables">The variables associated with the digital form.</param>
  124. /// <param name="values">The values to save.</param>
  125. public static void SerializeFormData(IDigitalFormInstance form, ICollection<DigitalFormVariable> variables, Dictionary<string, object?> values)
  126. {
  127. var blob = new Dictionary<string, object?>();
  128. foreach (var variable in variables)
  129. {
  130. if (variable.IsBlob() && values.TryGetValue(variable.Code, out var value))
  131. {
  132. var id = Guid.NewGuid().ToString();
  133. blob[id] = value;
  134. values[variable.Code] = id;
  135. }
  136. }
  137. form.FormData = Serialization.Serialize(values);
  138. form.BlobData = Serialization.Serialize(blob);
  139. }
  140. private static string? GetVariableData(DigitalFormVariable variable, object value)
  141. {
  142. // TODO: Replace with a function on DFLayoutField or something
  143. var fieldType = variable.FieldType();
  144. if (fieldType == typeof(DFLayoutEmbeddedImage)
  145. || fieldType == typeof(DFLayoutSignaturePad))
  146. {
  147. if (value is byte[]) return IsValidImage((byte[])value) ? Convert.ToBase64String((byte[])value) : null;
  148. var str = value.ToString();
  149. return IsValidImage(str) ? str : null;
  150. }
  151. else if(fieldType == typeof(DFLayoutMultiImage))
  152. {
  153. if (value is JArray || value is IEnumerable<string>) return Serialization.Serialize(value);
  154. return null;
  155. }
  156. return variable.FormatValue(value);
  157. }
  158. /// <summary>
  159. /// Generates a database FormData from a dictionary of objects. Returns null if the data is invalid (specifically if
  160. /// any required fields are not present.
  161. /// </summary>
  162. /// <param name="values">.NET objects</param>
  163. /// <param name="variables">The variables of the form needed to be encoded</param>
  164. /// <returns>A string with JSON-encoded FormData, or null if validation requirements are not met.</returns>
  165. public static string? GenerateFormData(Dictionary<string, object> values, IEnumerable<DigitalFormVariable> variables, Entity entity)
  166. {
  167. var data = new Dictionary<string, string>();
  168. foreach (var variable in variables)
  169. if (values.TryGetValue(variable.Code, out var value))
  170. {
  171. var properties = variable.CreateProperties();
  172. if (!string.IsNullOrWhiteSpace(properties.Property))
  173. {
  174. if (variable.FieldType() == typeof(DFLayoutLookupField))
  175. {
  176. if (values.TryGetValue($"{variable.Code}$ID", out var idStr) && Guid.TryParse((string)idStr, out var id))
  177. CoreUtils.SetPropertyValue(entity, properties.Property, id);
  178. }
  179. else
  180. {
  181. CoreUtils.SetPropertyValue(entity, properties.Property, value);
  182. }
  183. }
  184. if (value != null)
  185. {
  186. var varData = GetVariableData(variable, value);
  187. if (varData != null)
  188. data[variable.Code] = varData;
  189. }
  190. }
  191. else if (variable.Required)
  192. {
  193. return null;
  194. }
  195. return Serialization.Serialize(data);
  196. }
  197. /// <summary>
  198. /// Creates a dictionary of objects from a database FormData
  199. /// </summary>
  200. /// <param name="formData">A string with JSON-encoded FormData</param>
  201. /// <param name="variables">The variables of the form needed to be encoded</param>
  202. /// <returns></returns>
  203. public static Dictionary<string, object> ParseFormData(string formData, IEnumerable<DigitalFormVariable> variables, Entity entity)
  204. {
  205. var data = new Dictionary<string, object>();
  206. // Could be null
  207. var formObject = Serialization.Deserialize<Dictionary<string, object>>(formData);
  208. foreach (var variable in variables)
  209. {
  210. object? value = null;
  211. var code = variable.Code;
  212. var properties = variable.CreateProperties();
  213. if (!string.IsNullOrWhiteSpace(properties.Property))
  214. {
  215. value = CoreUtils.GetPropertyValue(entity, properties.Property);
  216. if (variable.FieldType() == typeof(DFLayoutLookupField))
  217. {
  218. code = variable.Code + "$ID";
  219. }
  220. }
  221. else if (value == null)
  222. formObject?.TryGetValue(variable.Code, out value);
  223. if (value != null)
  224. {
  225. data[code] = variable.ParseValue(value);
  226. }
  227. }
  228. return data;
  229. }
  230. }
  231. }