@kwiz/common 1.0.107 → 1.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.github/workflows/npm-publish.yml +24 -24
  2. package/.madgerc +2 -2
  3. package/LICENSE +21 -21
  4. package/fix-folder-imports.js +26 -26
  5. package/lib/cjs/helpers/browser.js +9 -5
  6. package/lib/cjs/helpers/browser.js.map +1 -1
  7. package/lib/cjs/types/libs/msal.types.js +26 -26
  8. package/lib/cjs/types/libs/msal.types.js.map +1 -1
  9. package/lib/cjs/types/sharepoint.utils.types.js +9 -1
  10. package/lib/cjs/types/sharepoint.utils.types.js.map +1 -1
  11. package/lib/cjs/utils/sharepoint.rest/file.folder.js +4 -4
  12. package/lib/cjs/utils/sharepoint.rest/file.folder.js.map +1 -1
  13. package/lib/cjs/utils/sharepoint.rest/list.js +60 -1
  14. package/lib/cjs/utils/sharepoint.rest/list.js.map +1 -1
  15. package/lib/cjs/utils/sharepoint.rest/user.js +11 -11
  16. package/lib/esm/helpers/browser.js +9 -5
  17. package/lib/esm/helpers/browser.js.map +1 -1
  18. package/lib/esm/types/libs/msal.types.js +26 -26
  19. package/lib/esm/types/libs/msal.types.js.map +1 -1
  20. package/lib/esm/types/sharepoint.utils.types.js +8 -0
  21. package/lib/esm/types/sharepoint.utils.types.js.map +1 -1
  22. package/lib/esm/utils/sharepoint.rest/file.folder.js +4 -4
  23. package/lib/esm/utils/sharepoint.rest/file.folder.js.map +1 -1
  24. package/lib/esm/utils/sharepoint.rest/list.js +57 -0
  25. package/lib/esm/utils/sharepoint.rest/list.js.map +1 -1
  26. package/lib/esm/utils/sharepoint.rest/user.js +11 -11
  27. package/lib/types/types/libs/msal.types.d.ts +8 -3
  28. package/lib/types/types/sharepoint.utils.types.d.ts +17 -0
  29. package/lib/types/utils/sharepoint.rest/list.d.ts +7 -1
  30. package/package.json +81 -81
  31. package/readme.md +17 -17
  32. package/src/_dependencies.ts +12 -12
  33. package/src/config.ts +17 -17
  34. package/src/helpers/Guid.ts +181 -181
  35. package/src/helpers/base64.ts +173 -173
  36. package/src/helpers/browser.test.js +13 -13
  37. package/src/helpers/browser.ts +1448 -1448
  38. package/src/helpers/browserinfo.ts +292 -292
  39. package/src/helpers/collections.base.test.js +25 -25
  40. package/src/helpers/collections.base.ts +437 -437
  41. package/src/helpers/collections.ts +107 -107
  42. package/src/helpers/color.ts +54 -54
  43. package/src/helpers/cookies.ts +59 -59
  44. package/src/helpers/date.test.js +119 -119
  45. package/src/helpers/date.ts +188 -188
  46. package/src/helpers/debug.ts +186 -186
  47. package/src/helpers/diagrams.ts +43 -43
  48. package/src/helpers/emails.ts +6 -6
  49. package/src/helpers/eval.ts +5 -5
  50. package/src/helpers/file.test.js +50 -50
  51. package/src/helpers/file.ts +63 -63
  52. package/src/helpers/flatted.ts +149 -149
  53. package/src/helpers/functions.ts +16 -16
  54. package/src/helpers/graph/calendar.types.ts +10 -10
  55. package/src/helpers/http.ts +69 -69
  56. package/src/helpers/images.ts +22 -22
  57. package/src/helpers/json.ts +44 -44
  58. package/src/helpers/md5.ts +189 -189
  59. package/src/helpers/objects.test.js +33 -33
  60. package/src/helpers/objects.ts +274 -274
  61. package/src/helpers/promises.test.js +37 -37
  62. package/src/helpers/promises.ts +165 -165
  63. package/src/helpers/random.ts +27 -27
  64. package/src/helpers/scheduler/scheduler.test.js +103 -103
  65. package/src/helpers/scheduler/scheduler.ts +131 -131
  66. package/src/helpers/sharepoint.ts +785 -785
  67. package/src/helpers/strings.test.js +122 -122
  68. package/src/helpers/strings.ts +337 -337
  69. package/src/helpers/typecheckers.test.js +34 -34
  70. package/src/helpers/typecheckers.ts +266 -266
  71. package/src/helpers/url.test.js +43 -43
  72. package/src/helpers/url.ts +207 -207
  73. package/src/helpers/urlhelper.ts +111 -111
  74. package/src/index.ts +6 -6
  75. package/src/types/auth.ts +54 -54
  76. package/src/types/common.types.ts +15 -15
  77. package/src/types/flatted.types.ts +59 -59
  78. package/src/types/globals.types.ts +6 -6
  79. package/src/types/graph/calendar.types.ts +80 -80
  80. package/src/types/knownscript.types.ts +18 -18
  81. package/src/types/libs/datajs.types.ts +28 -28
  82. package/src/types/libs/ics.types.ts +30 -30
  83. package/src/types/libs/msal.types.ts +57 -49
  84. package/src/types/locales.ts +125 -125
  85. package/src/types/localstoragecache.types.ts +8 -8
  86. package/src/types/location.types.ts +27 -27
  87. package/src/types/moment.ts +11 -11
  88. package/src/types/regex.types.ts +16 -16
  89. package/src/types/rest.types.ts +95 -95
  90. package/src/types/sharepoint.types.ts +1466 -1466
  91. package/src/types/sharepoint.utils.types.ts +306 -287
  92. package/src/utils/auth/common.ts +74 -74
  93. package/src/utils/auth/discovery.test.js +12 -12
  94. package/src/utils/auth/discovery.ts +132 -132
  95. package/src/utils/base64.ts +27 -27
  96. package/src/utils/consolelogger.ts +320 -320
  97. package/src/utils/date.ts +172 -172
  98. package/src/utils/emails.ts +24 -24
  99. package/src/utils/knownscript.ts +286 -286
  100. package/src/utils/localstoragecache.ts +446 -446
  101. package/src/utils/rest.ts +501 -501
  102. package/src/utils/script.ts +170 -170
  103. package/src/utils/sharepoint.rest/common.ts +154 -154
  104. package/src/utils/sharepoint.rest/date.ts +62 -62
  105. package/src/utils/sharepoint.rest/file.folder.ts +598 -598
  106. package/src/utils/sharepoint.rest/item.ts +547 -547
  107. package/src/utils/sharepoint.rest/list.ts +1548 -1482
  108. package/src/utils/sharepoint.rest/listutils/GetListItemsByCaml.ts +774 -774
  109. package/src/utils/sharepoint.rest/listutils/GetListItemsById.ts +275 -275
  110. package/src/utils/sharepoint.rest/listutils/common.ts +206 -206
  111. package/src/utils/sharepoint.rest/location.ts +141 -141
  112. package/src/utils/sharepoint.rest/navigation-links.ts +86 -86
  113. package/src/utils/sharepoint.rest/user-search.ts +252 -252
  114. package/src/utils/sharepoint.rest/user.ts +491 -491
  115. package/src/utils/sharepoint.rest/web.ts +1384 -1384
  116. package/src/utils/sod.ts +194 -194
@@ -1,547 +1,547 @@
1
- import { chunkArray } from "../../helpers/collections.base";
2
- import { hasOwnProperty } from "../../helpers/objects";
3
- import { promiseNParallel } from "../../helpers/promises";
4
- import { isBoolean, isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isObject, isString } from "../../helpers/typecheckers";
5
- import { encodeURIComponentEX } from "../../helpers/url";
6
- import { IDictionary } from "../../types/common.types";
7
- import { jsonTypes } from "../../types/rest.types";
8
- import { DateTimeFieldFormatType, IAttachmentInfo, IFieldCurrencyInfo, IFieldDateTimeInfo, IFieldInfoEX, IFieldNumberInfo, TaxonomyValueType } from "../../types/sharepoint.types";
9
- import { IRestItem } from "../../types/sharepoint.utils.types";
10
- import { LocaleKnownScript } from "../../utils/knownscript";
11
- import { ConsoleLogger } from "../consolelogger";
12
- import { GetJson, GetJsonSync } from "../rest";
13
- import { GetFieldNameFromRawValues, GetSiteUrl, __getSPRestErrorData, getFieldNameForUpdate } from "./common";
14
- import { GetList, GetListFields, GetListFieldsAsHash, GetListRestUrl } from "./list";
15
- import { GetUser, GetUserSync } from "./user";
16
-
17
- const logger = ConsoleLogger.get("SharePoint.Rest.Item");
18
-
19
- /** can only select FileSizeDisplay in REST api */
20
- export const FileSizeColumnInternalNames = ["FileSizeDisplay", "File_x0020_Size"];
21
-
22
- function _getListItemSelectExpandFields(fields: string[], listFields: IFieldInfoEX[]) {
23
- var $selectFields = [];
24
- var $expandFields = [];
25
-
26
- fields.forEach((fieldName) => {
27
- if (FileSizeColumnInternalNames.includes(fieldName)) {
28
- $selectFields.push(FileSizeColumnInternalNames[0]);//for some reason, can't select File_x0020_Size
29
- }
30
- else {
31
- let field = listFields.filter((listField) => { return listField.InternalName === fieldName; })[0];
32
- if (!isNullOrUndefined(field)) {
33
- if (field.TypeAsString === "User" || field.TypeAsString === "UserMulti") {
34
- $selectFields.push(`${field.InternalName}/ID`);
35
- $selectFields.push(`${field.InternalName}/Name`);
36
- $selectFields.push(`${field.InternalName}/UserName`);
37
- $selectFields.push(`${field.InternalName}/EMail`);
38
- $selectFields.push(`${field.InternalName}/Title`);
39
- $expandFields.push(field.InternalName);
40
- } else {
41
- $selectFields.push(field.InternalName);
42
- }
43
- }
44
- }
45
- });
46
-
47
- return {
48
- expandFields: $expandFields,
49
- selectFields: $selectFields
50
- };
51
- }
52
-
53
- function _parseValueFromRawValue(rawValue: any, asDisplayValue = false) {
54
- if (!isNullOrUndefined(rawValue)) {
55
- if (rawValue["ID"] && rawValue["Title"] && rawValue["Name"]) { //expanded user field from rest request
56
- return !asDisplayValue ? rawValue["ID"] : rawValue["Title"];
57
- } else if (Array.isArray(rawValue)) {
58
- return rawValue.map((value) => {
59
- if (value["ID"] && value["Title"] && value["Name"]) { //expanded user field from rest request
60
- return !asDisplayValue ? value["ID"] : value["Title"];
61
- }
62
- return value;
63
- }).filter((value) => {
64
- return value !== null;
65
- });
66
- } else {
67
- return rawValue;
68
- }
69
- }
70
- }
71
-
72
- async function _getListItemRawFieldValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: any; }> {
73
- siteUrl = GetSiteUrl(siteUrl);
74
-
75
- options = options || {};
76
-
77
- let listFields = await GetListFields(siteUrl, listIdOrTitle);
78
- var { selectFields, expandFields } = _getListItemSelectExpandFields(fields, listFields);
79
-
80
- var $select = `$select=` + encodeURIComponent(selectFields.length ? `${selectFields.join(',')}` : fields.join(','));
81
- var $expand = expandFields.length ? `$expand=${encodeURIComponent(expandFields.join(','))}` : "";
82
-
83
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})?${$select}&${$expand}`;
84
- let result = await GetJson<{ d: { [field: string]: any; }; }>(url, null, { allowCache: options.refreshCache !== true });
85
-
86
- var values = {};
87
-
88
- if (result && typeof (result.d) !== "undefined") {
89
- var rawValues = result.d;
90
- fields.forEach((fieldName) => {
91
- let rawValue = (FileSizeColumnInternalNames.includes(fieldName)) ? rawValues[FileSizeColumnInternalNames[0]] : rawValues[fieldName];
92
- if (!isNullOrUndefined(rawValue)) {
93
- values[fieldName] = rawValue;
94
- }
95
- });
96
- }
97
-
98
- return values;
99
- }
100
-
101
- export function GetListItemFieldDisplayValueSync(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string): string {
102
- return GetListItemFieldDisplayValuesSync(siteUrl, listIdOrTitle, itemId, [field])[field];
103
- }
104
- export function GetListItemFieldDisplayValuesSync(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[]): IDictionary<string> {
105
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/FieldValuesAsText?$select=${fields.join(',')}`;
106
- let result = GetJsonSync<IDictionary<string>>(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata });
107
- return result.success ? result.result : {};
108
- }
109
-
110
- export async function GetListItemFieldDisplayValue(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string, options?: { refreshCache?: boolean; }): Promise<string> {
111
- var values = await GetListItemFieldDisplayValues(siteUrl, listIdOrTitle, itemId, [field], options);
112
- return values && values[field];
113
- }
114
-
115
- export async function GetListItemFieldDisplayValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: string; }> {
116
- var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options);
117
-
118
- var values = {};
119
- Object.keys(rawValues).forEach(key => {
120
- var fieldValue = _parseValueFromRawValue(rawValues[key], true);
121
- if (!isNullOrUndefined(fieldValue)) {
122
- values[key] = fieldValue;
123
- }
124
- });
125
-
126
- return values;
127
- }
128
-
129
- export async function GetListItemFieldValue(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string, options?: { refreshCache?: boolean; }): Promise<any> {
130
- var values = await GetListItemFieldValues(siteUrl, listIdOrTitle, itemId, [field], options);
131
- return values && values[field];
132
- }
133
-
134
- export async function GetListItemFieldValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: any; }> {
135
- var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options);
136
-
137
- var values = {};
138
- Object.keys(rawValues).forEach(key => {
139
- var fieldValue = _parseValueFromRawValue(rawValues[key]);
140
- if (!isNullOrUndefined(fieldValue)) {
141
- values[key] = fieldValue;
142
- }
143
- });
144
-
145
- return values;
146
- }
147
-
148
- /** Returns version array, newest version first */
149
- export async function GetListItemFieldValuesHistory(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }) {
150
- siteUrl = GetSiteUrl(siteUrl);
151
-
152
- options = options || {};
153
- var $select = isNotEmptyArray(fields) ? `$select=` + encodeURIComponent(`${fields.join(',')}`) : "";
154
-
155
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/versions?${$select}`;
156
- let result = await GetJson<{ value: IRestItem[]; }>(url, null, {
157
- allowCache: options.refreshCache !== true,
158
- jsonMetadata: jsonTypes.nometadata
159
- });
160
-
161
- return result && result.value || [];
162
- }
163
-
164
- export async function DeleteListItem(siteUrl: string, listIdOrTitle: string, itemId: number | string): Promise<{ deleted: boolean; errorMessage?: string; }> {
165
- siteUrl = GetSiteUrl(siteUrl);
166
-
167
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})`;
168
-
169
- let result: { deleted: boolean; errorMessage?: string; } = { deleted: true };
170
- try {
171
- await GetJson<string>(url, null, { method: "POST", spWebUrl: siteUrl, xHttpMethod: "DELETE" });
172
- //empty string means deleted
173
- } catch (e) {
174
- result.deleted = false;
175
- result.errorMessage = __getSPRestErrorData(e).message;
176
- }
177
-
178
- return result;
179
- }
180
-
181
- export async function RecycleListItem(siteUrl: string, listIdOrTitle: string, itemId: number | string): Promise<{ recycled: boolean; errorMessage?: string; }> {
182
- siteUrl = GetSiteUrl(siteUrl);
183
-
184
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/recycle()`;
185
-
186
- let result: { recycled: boolean; errorMessage?: string; } = { recycled: true };
187
- try {
188
- await GetJson<{ d: { Recycle: string; }; }>(url, null, { method: "POST", spWebUrl: siteUrl });
189
- //value.d.Recycle will hold guide reference id
190
- } catch (e) {
191
- result.recycled = false;
192
- result.errorMessage = __getSPRestErrorData(e).message;
193
- }
194
-
195
- return result;
196
- }
197
-
198
- export async function GetListItemAttachments(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<IAttachmentInfo[]> {
199
- siteUrl = GetSiteUrl(siteUrl);
200
-
201
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles`;
202
-
203
- try {
204
- let result = await GetJson<{ d: { results: IAttachmentInfo[]; }; }>(url, null, { includeDigestInGet: true });
205
- let attachmentFiles = result.d && result.d.results ? result.d.results : [];
206
- return attachmentFiles;
207
- } catch (e) {
208
- }
209
- return [];
210
- }
211
-
212
- export async function GetListItemsAttachments(siteUrl: string, listIdOrTitle: string, itemIds: number[]): Promise<{ Id: number, AttachmentFiles: IAttachmentInfo[] }[]> {
213
- siteUrl = GetSiteUrl(siteUrl);
214
- let chunks = chunkArray(itemIds, 30);
215
- let select = `$select=ID,AttachmentFiles`;
216
- let expand = `$expand=AttachmentFiles`;
217
- let baseUrl = GetListRestUrl(siteUrl, listIdOrTitle) + `/items`;
218
- let promises = chunks.map((chunk) => {
219
- return () => {
220
- let filter = `$filter=${chunk.map(i => `ID eq ${i}`).join(" or ")}`;
221
- let url = `${baseUrl}?${select}&${filter}&${expand}`
222
- return GetJson<{ value: { Id: number, AttachmentFiles: IAttachmentInfo[] } }>(url, null, { includeDigestInGet: true, jsonMetadata: jsonTypes.nometadata });
223
- };
224
- });
225
- try {
226
- let result = await promiseNParallel(promises, 5);
227
- return result && result.length > 0 ? result.map(v => v.value) : [];
228
- } catch {
229
-
230
- }
231
- return [];
232
- }
233
-
234
- export async function AddAttachment(siteUrl: string, listIdOrTitle: string, itemId: number, filename: string, buffer: ArrayBuffer) {
235
- siteUrl = GetSiteUrl(siteUrl);
236
-
237
- //Issue 999
238
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles/add(FileName='${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`;
239
-
240
- try {
241
- let result = await GetJson<{ d: IAttachmentInfo; }>(url, buffer, { includeDigestInPost: true, method: "POST" });
242
- let attachmentFile = result && result.d;
243
- return attachmentFile;
244
- } catch (e) {
245
- }
246
- return null;
247
- }
248
-
249
- export async function DeleteAttachment(siteUrl: string, listIdOrTitle: string, itemId: number, filename: string) {
250
- siteUrl = GetSiteUrl(siteUrl);
251
-
252
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles('${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`;
253
-
254
- try {
255
- let result = await GetJson<{ d: IAttachmentInfo; }>(url, null, { includeDigestInGet: true, includeDigestInPost: true, xHttpMethod: "DELETE" });
256
- let attachmentFile = result && result.d;
257
- return attachmentFile;
258
- } catch (e) {
259
- }
260
- return null;
261
- }
262
-
263
- //** Update value of taxonomy multi-value field. See issue 7585 for more info */
264
- export async function UpdateMultiTaxonomyValue(siteUrl: string, listIdOrTitle: string, itemId: number,
265
- updates: IDictionary<TaxonomyValueType[]>) {
266
-
267
- let fields = updates && Object.keys(updates) || [];
268
-
269
- if (isNullOrEmptyArray(fields)) return [];
270
-
271
- siteUrl = GetSiteUrl(siteUrl);
272
-
273
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`;
274
-
275
- try {
276
- let result = await GetJson<{
277
- d: {
278
- ValidateUpdateListItem: {
279
- results: {
280
- ErrorCode: number;
281
- ErrorMEssage?: string;
282
- FieldName: string;
283
- FieldValue: string;
284
- HasException: boolean;
285
- ItemId: number;
286
- }[];
287
- };
288
- };
289
- }>(url, JSON.stringify({
290
- bNewDocumentUpdate: false,
291
- checkInComment: null,
292
- formValues: fields.map(field => ({
293
- ErrorMessage: null,
294
- FieldName: field,
295
- FieldValue: updates[field].map(v => `${v.Label}|${v.TermGuid};`).join(''),
296
- HasException: false
297
- }))
298
- }), { includeDigestInPost: true, method: "POST" });
299
- return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || [];
300
- } catch (e) {
301
- logger.error(`Error updating UpdateMultiTaxonomyValue ${e}`);
302
- }
303
- return fields.map(f => ({ field: f, error: 'Unspecified update error' }));
304
- }
305
-
306
- export async function AddItem(siteUrl: string, listIdOrTitle: string, fieldValues: IDictionary<any>) {
307
- //we must force creating even if no values, otherwise the item won't be created at all.
308
- return UpdateItem(siteUrl, listIdOrTitle, null, fieldValues, { updateIfNoFields: true });
309
- }
310
- export interface UpdateItemType {
311
- updateProps: {
312
- success: boolean;
313
- itemId: number;
314
- errorMessage?: undefined;
315
- } | {
316
- success: boolean;
317
- errorMessage: string;
318
- itemId: number;
319
- }
320
- }
321
- export async function UpdateItem(siteUrl: string, listIdOrTitle: string, itemId: number, fieldValues: IDictionary<any>, options?: { updateIfNoFields?: boolean; }) {
322
- var success = false;
323
- var error: string = null;
324
- try {
325
- siteUrl = GetSiteUrl(siteUrl);
326
-
327
- let isNewItem = itemId > 0 ? false : true;
328
- let listInfo = await GetList(siteUrl, listIdOrTitle);
329
- let fields = await GetListFieldsAsHash(siteUrl, listIdOrTitle);
330
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items${isNewItem ? '' : `(${itemId})`}`;
331
-
332
- var itemUpdateInfo: { [key: string]: any; } = {
333
- '__metadata': { 'type': `SP.Data.${listInfo.EntityTypeName}Item` }
334
- };
335
- let hasUpdates = false;
336
- Object.keys(fieldValues).forEach(updateField => {
337
- let listField = fields[updateField];
338
- if (listField)//make sure this field exists on the list
339
- {
340
- //todo: we might want to get the value first, make sure it is formatted correctly for the field type.
341
- itemUpdateInfo[getFieldNameForUpdate(listField)] = fieldValues[updateField];
342
- hasUpdates = true;
343
- }
344
- });
345
-
346
- if (!hasUpdates) {
347
- let forceUpdate = options && options.updateIfNoFields;
348
- if (!forceUpdate)
349
- return { success: true, itemId: itemId };
350
- }
351
-
352
- var xHttpMethod: "MERGE" = isNewItem ? null : "MERGE";
353
-
354
- try {
355
- let result = await GetJson<{ d: { Id: number; }; }>(url, JSON.stringify(itemUpdateInfo), { method: "POST", xHttpMethod: xHttpMethod });
356
- if (result)
357
- if (isNewItem)
358
- itemId = result.d.Id;// update item will not return data. only new item will.
359
- success = true;
360
- } catch (e) {
361
- error = __getSPRestErrorData(e).message;
362
- }
363
- } catch (e) {
364
- logger.group(() => logger.error(e), 'Update item failed');
365
- }
366
- return { success: success, errorMessage: error, itemId: itemId };
367
- }
368
- /** Get a sharepoint field value as text, from a rest item */
369
- export function GetSPRestItemValueAsText(item: IRestItem, field: IFieldInfoEX): string {
370
-
371
- var otherName = field.InternalName.replace(/_/g, "_x005f_");//sometimes _ gets replaced with _x005f_
372
- if (!isNullOrUndefined(item.FieldValuesAsText)) {
373
- if (hasOwnProperty(item.FieldValuesAsText, field.InternalName))
374
- return item.FieldValuesAsText[field.InternalName];
375
- if (hasOwnProperty(item.FieldValuesAsText, otherName))
376
- return item.FieldValuesAsText[otherName];
377
- }
378
-
379
- return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field).join(', ');
380
- }
381
- /** Get a sharepoint field value as text array, from a rest item */
382
- export function GetSPRestItemValueAsTextArray(item: IRestItem, field: IFieldInfoEX): string[] {
383
- //get value as text first
384
- let valueAsText = null;
385
- var otherName = field.InternalName.replace(/_/g, "_x005f_");//sometimes _ gets replaced with _x005f_
386
- if (!isNullOrUndefined(item.FieldValuesAsText)) {
387
- if (hasOwnProperty(item.FieldValuesAsText, field.InternalName))
388
- valueAsText = item.FieldValuesAsText[field.InternalName];
389
- if (hasOwnProperty(item.FieldValuesAsText, otherName))
390
- valueAsText = item.FieldValuesAsText[otherName];
391
- }
392
- if (!isNullOrEmptyString(valueAsText) && valueAsText.indexOf(',') < 0)//not empty, and we do not suspect a multi-value field
393
- return [valueAsText];
394
-
395
- return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field);
396
- }
397
- /** prefer to use GetSPRestValueAsText instead */
398
- export function GetSPFieldValueAsText(value: any, field: IFieldInfoEX): string[] {
399
- let locales = LocaleKnownScript.loadSync();
400
- let culture = locales.GetCurrentCulture();
401
-
402
- let rawValues: (string | number | boolean | Date | TaxonomyValueType | TaxonomyValueType[] | { Id: string | number; Title: string; })[] =
403
- isNullOrEmptyString(value)
404
- ? []
405
- : Array.isArray(value)
406
- ? value//value.raw is an array
407
- : [value];//value.raw is not an array - wrap it.
408
-
409
- let isLookup = field.TypeAsString === "Lookup" || field.TypeAsString === "LookupMulti";
410
- let isUser = field.TypeAsString === "User" || field.TypeAsString === "UserMulti";
411
- let isCounter = field.TypeAsString === "Counter" || field.TypeAsString === "Integer";
412
- let isTaxonomy = field.TypeAsString === "TaxonomyFieldType" || field.TypeAsString === "TaxonomyFieldTypeMulti";
413
- if (field.TypeAsString === "DateTime") {
414
- //Issue 8190 - date field might come as string
415
- rawValues = rawValues.map(v => isDate(v) ? v : new Date(v as string));
416
- }
417
- else if (isUser || isLookup) {
418
- rawValues = rawValues.map(v => isNumber(v) ? v : !isNullOrEmptyString(v && (v as any).Title) ? (v as any).Title : isNumber(v && (v as any).Id) ? (v as any).Id : null);
419
- }
420
-
421
- let textResults: string[] = [];
422
- if (isNotEmptyArray(rawValues)) {
423
- rawValues.forEach(raw => {
424
- if (isNullOrEmptyString(raw)) {/** skip */ }
425
- else if (isNumber(raw))
426
- if (isUser) {
427
- //todo - try not sync...
428
- try {
429
- let userInfo = GetUserSync(_spPageContextInfo.siteServerRelativeUrl, raw);
430
- textResults.push(userInfo.Title);
431
- } catch (e) {
432
- textResults.push(`${raw}`);
433
- }
434
- }
435
- else if (isLookup) {
436
- //todo - not supported
437
- try {
438
- textResults.push(`Lookup #${raw}`);
439
- }
440
- catch (e) {
441
- textResults.push(`${raw}`);
442
- }
443
- } else if (isCounter) {
444
- textResults.push(raw.toString());
445
- } else {
446
- textResults.push(locales.NumberToString(raw, culture, {
447
- isCurrency: isNumber((field as IFieldCurrencyInfo).CurrencyLocaleId),
448
- isPercent: (field as IFieldNumberInfo).ShowAsPercentage
449
- }));
450
- }
451
- else if (isTaxonomy) {
452
- if (isNotEmptyArray(raw)) {
453
- textResults.push(raw.map(t => `${t.Label}|${t.TermGuid}`).join(';'));
454
- } else if (isObject(raw) && raw !== null && 'Label' in raw) {
455
- textResults.push(raw.Label || '');
456
- }
457
- }
458
- else if (isString(raw))
459
- textResults.push(raw);
460
- else if (isBoolean(raw)) {
461
- textResults.push(raw ? "Yes" : "No");
462
- }
463
- else if (isDate(raw)) {
464
- textResults.push(locales.DateToString(raw, culture, {
465
- includeDate: true,
466
- includeTime: (field as IFieldDateTimeInfo).DisplayFormat === DateTimeFieldFormatType.DateTime
467
- }));
468
- }
469
- });
470
- }
471
- return textResults;
472
- }
473
-
474
- /** set an existing item system info: author, editor, created and modified dates */
475
- export async function SetItemCreatedModifiedInfo(siteUrl: string, listIdOrTitle: string, itemId: number,
476
- updates: { Created?: string | Date; Modified?: string | Date; AuthorId?: number; EditorId?: number; }) {
477
-
478
- let updateValues: IDictionary<string> = {};
479
-
480
- let fields = updates && Object.keys(updates) || [];
481
- if (!isNullOrEmptyString(updates.Created))//date must be yyyy-MM-dd hh:mm:ss
482
- updateValues.Created = (isString(updates.Created) ? new Date(updates.Created) : updates.Created).toISOString().replace('T', ' ').split('.')[0];
483
- if (!isNullOrEmptyString(updates.Modified))
484
- updateValues.Modified = (isString(updates.Modified) ? new Date(updates.Modified) : updates.Modified).toISOString().replace('T', ' ').split('.')[0];
485
- if (updates.AuthorId > 0) {
486
- let asUser = await GetUser(siteUrl, updates.AuthorId);
487
- updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`;//[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}]
488
- }
489
- if (updates.EditorId > 0) {
490
- let asUser = await GetUser(siteUrl, updates.EditorId);
491
- updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`;//[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}]
492
- }
493
-
494
- if (isNullOrEmptyArray(fields)) return [];
495
-
496
- siteUrl = GetSiteUrl(siteUrl);
497
-
498
- let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`;
499
-
500
- try {
501
- let result = await GetJson<{
502
- d: {
503
- ValidateUpdateListItem: {
504
- results: {
505
- ErrorCode: number;
506
- ErrorMEssage?: string;
507
- FieldName: string;
508
- FieldValue: string;
509
- HasException: boolean;
510
- ItemId: number;
511
- }[];
512
- };
513
- };
514
- }>(url, JSON.stringify({
515
- formValues: fields.map(field => ({
516
- FieldName: field,
517
- FieldValue: updateValues[field]
518
- }))
519
- }), { method: "POST" });
520
- return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || [];
521
- } catch (e) {
522
- logger.error(`Error updating values ${e}`);
523
- }
524
- return fields.map(f => ({ field: f, error: 'Unspecified update error' }));
525
- }
526
-
527
- export async function ListItemHasUniquePermissions(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<boolean> {
528
- let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/?$select=hasuniqueroleassignments`;
529
- let has = await GetJson<{ HasUniqueRoleAssignments: boolean }>(url, undefined, { allowCache: false, jsonMetadata: jsonTypes.nometadata });
530
- return has.HasUniqueRoleAssignments === true;
531
- }
532
- export async function RestoreListItemPermissionInheritance(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<void> {
533
- let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/ResetRoleInheritance`;
534
- await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
535
- }
536
- export async function BreakListItemPermissionInheritance(siteUrl: string, listIdOrTitle: string, itemId: number, clear = true): Promise<void> {
537
- let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/breakroleinheritance(copyRoleAssignments=${clear ? 'false' : 'true'}, clearSubscopes=true)`;
538
- await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
539
- }
540
- export async function AssignListItemPermission(siteUrl: string, listIdOrTitle: string, itemId: number, principalId: number, roleId: number) {
541
- let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/addroleassignment(principalid=${principalId},roleDefId=${roleId})`;
542
- await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
543
- }
544
- export async function RemoveListItemPermission(siteUrl: string, listIdOrTitle: string, itemId: number, principalId: number, roleId: number) {
545
- let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/removeroleassignment(principalid=${principalId},roleDefId=${roleId})`;
546
- await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
547
- }
1
+ import { chunkArray } from "../../helpers/collections.base";
2
+ import { hasOwnProperty } from "../../helpers/objects";
3
+ import { promiseNParallel } from "../../helpers/promises";
4
+ import { isBoolean, isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isObject, isString } from "../../helpers/typecheckers";
5
+ import { encodeURIComponentEX } from "../../helpers/url";
6
+ import { IDictionary } from "../../types/common.types";
7
+ import { jsonTypes } from "../../types/rest.types";
8
+ import { DateTimeFieldFormatType, IAttachmentInfo, IFieldCurrencyInfo, IFieldDateTimeInfo, IFieldInfoEX, IFieldNumberInfo, TaxonomyValueType } from "../../types/sharepoint.types";
9
+ import { IRestItem } from "../../types/sharepoint.utils.types";
10
+ import { LocaleKnownScript } from "../../utils/knownscript";
11
+ import { ConsoleLogger } from "../consolelogger";
12
+ import { GetJson, GetJsonSync } from "../rest";
13
+ import { GetFieldNameFromRawValues, GetSiteUrl, __getSPRestErrorData, getFieldNameForUpdate } from "./common";
14
+ import { GetList, GetListFields, GetListFieldsAsHash, GetListRestUrl } from "./list";
15
+ import { GetUser, GetUserSync } from "./user";
16
+
17
+ const logger = ConsoleLogger.get("SharePoint.Rest.Item");
18
+
19
+ /** can only select FileSizeDisplay in REST api */
20
+ export const FileSizeColumnInternalNames = ["FileSizeDisplay", "File_x0020_Size"];
21
+
22
+ function _getListItemSelectExpandFields(fields: string[], listFields: IFieldInfoEX[]) {
23
+ var $selectFields = [];
24
+ var $expandFields = [];
25
+
26
+ fields.forEach((fieldName) => {
27
+ if (FileSizeColumnInternalNames.includes(fieldName)) {
28
+ $selectFields.push(FileSizeColumnInternalNames[0]);//for some reason, can't select File_x0020_Size
29
+ }
30
+ else {
31
+ let field = listFields.filter((listField) => { return listField.InternalName === fieldName; })[0];
32
+ if (!isNullOrUndefined(field)) {
33
+ if (field.TypeAsString === "User" || field.TypeAsString === "UserMulti") {
34
+ $selectFields.push(`${field.InternalName}/ID`);
35
+ $selectFields.push(`${field.InternalName}/Name`);
36
+ $selectFields.push(`${field.InternalName}/UserName`);
37
+ $selectFields.push(`${field.InternalName}/EMail`);
38
+ $selectFields.push(`${field.InternalName}/Title`);
39
+ $expandFields.push(field.InternalName);
40
+ } else {
41
+ $selectFields.push(field.InternalName);
42
+ }
43
+ }
44
+ }
45
+ });
46
+
47
+ return {
48
+ expandFields: $expandFields,
49
+ selectFields: $selectFields
50
+ };
51
+ }
52
+
53
+ function _parseValueFromRawValue(rawValue: any, asDisplayValue = false) {
54
+ if (!isNullOrUndefined(rawValue)) {
55
+ if (rawValue["ID"] && rawValue["Title"] && rawValue["Name"]) { //expanded user field from rest request
56
+ return !asDisplayValue ? rawValue["ID"] : rawValue["Title"];
57
+ } else if (Array.isArray(rawValue)) {
58
+ return rawValue.map((value) => {
59
+ if (value["ID"] && value["Title"] && value["Name"]) { //expanded user field from rest request
60
+ return !asDisplayValue ? value["ID"] : value["Title"];
61
+ }
62
+ return value;
63
+ }).filter((value) => {
64
+ return value !== null;
65
+ });
66
+ } else {
67
+ return rawValue;
68
+ }
69
+ }
70
+ }
71
+
72
+ async function _getListItemRawFieldValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: any; }> {
73
+ siteUrl = GetSiteUrl(siteUrl);
74
+
75
+ options = options || {};
76
+
77
+ let listFields = await GetListFields(siteUrl, listIdOrTitle);
78
+ var { selectFields, expandFields } = _getListItemSelectExpandFields(fields, listFields);
79
+
80
+ var $select = `$select=` + encodeURIComponent(selectFields.length ? `${selectFields.join(',')}` : fields.join(','));
81
+ var $expand = expandFields.length ? `$expand=${encodeURIComponent(expandFields.join(','))}` : "";
82
+
83
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})?${$select}&${$expand}`;
84
+ let result = await GetJson<{ d: { [field: string]: any; }; }>(url, null, { allowCache: options.refreshCache !== true });
85
+
86
+ var values = {};
87
+
88
+ if (result && typeof (result.d) !== "undefined") {
89
+ var rawValues = result.d;
90
+ fields.forEach((fieldName) => {
91
+ let rawValue = (FileSizeColumnInternalNames.includes(fieldName)) ? rawValues[FileSizeColumnInternalNames[0]] : rawValues[fieldName];
92
+ if (!isNullOrUndefined(rawValue)) {
93
+ values[fieldName] = rawValue;
94
+ }
95
+ });
96
+ }
97
+
98
+ return values;
99
+ }
100
+
101
+ export function GetListItemFieldDisplayValueSync(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string): string {
102
+ return GetListItemFieldDisplayValuesSync(siteUrl, listIdOrTitle, itemId, [field])[field];
103
+ }
104
+ export function GetListItemFieldDisplayValuesSync(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[]): IDictionary<string> {
105
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/FieldValuesAsText?$select=${fields.join(',')}`;
106
+ let result = GetJsonSync<IDictionary<string>>(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata });
107
+ return result.success ? result.result : {};
108
+ }
109
+
110
+ export async function GetListItemFieldDisplayValue(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string, options?: { refreshCache?: boolean; }): Promise<string> {
111
+ var values = await GetListItemFieldDisplayValues(siteUrl, listIdOrTitle, itemId, [field], options);
112
+ return values && values[field];
113
+ }
114
+
115
+ export async function GetListItemFieldDisplayValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: string; }> {
116
+ var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options);
117
+
118
+ var values = {};
119
+ Object.keys(rawValues).forEach(key => {
120
+ var fieldValue = _parseValueFromRawValue(rawValues[key], true);
121
+ if (!isNullOrUndefined(fieldValue)) {
122
+ values[key] = fieldValue;
123
+ }
124
+ });
125
+
126
+ return values;
127
+ }
128
+
129
+ export async function GetListItemFieldValue(siteUrl: string, listIdOrTitle: string, itemId: number | string, field: string, options?: { refreshCache?: boolean; }): Promise<any> {
130
+ var values = await GetListItemFieldValues(siteUrl, listIdOrTitle, itemId, [field], options);
131
+ return values && values[field];
132
+ }
133
+
134
+ export async function GetListItemFieldValues(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }): Promise<{ [fieldName: string]: any; }> {
135
+ var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options);
136
+
137
+ var values = {};
138
+ Object.keys(rawValues).forEach(key => {
139
+ var fieldValue = _parseValueFromRawValue(rawValues[key]);
140
+ if (!isNullOrUndefined(fieldValue)) {
141
+ values[key] = fieldValue;
142
+ }
143
+ });
144
+
145
+ return values;
146
+ }
147
+
148
+ /** Returns version array, newest version first */
149
+ export async function GetListItemFieldValuesHistory(siteUrl: string, listIdOrTitle: string, itemId: number | string, fields: string[], options?: { refreshCache?: boolean; }) {
150
+ siteUrl = GetSiteUrl(siteUrl);
151
+
152
+ options = options || {};
153
+ var $select = isNotEmptyArray(fields) ? `$select=` + encodeURIComponent(`${fields.join(',')}`) : "";
154
+
155
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/versions?${$select}`;
156
+ let result = await GetJson<{ value: IRestItem[]; }>(url, null, {
157
+ allowCache: options.refreshCache !== true,
158
+ jsonMetadata: jsonTypes.nometadata
159
+ });
160
+
161
+ return result && result.value || [];
162
+ }
163
+
164
+ export async function DeleteListItem(siteUrl: string, listIdOrTitle: string, itemId: number | string): Promise<{ deleted: boolean; errorMessage?: string; }> {
165
+ siteUrl = GetSiteUrl(siteUrl);
166
+
167
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})`;
168
+
169
+ let result: { deleted: boolean; errorMessage?: string; } = { deleted: true };
170
+ try {
171
+ await GetJson<string>(url, null, { method: "POST", spWebUrl: siteUrl, xHttpMethod: "DELETE" });
172
+ //empty string means deleted
173
+ } catch (e) {
174
+ result.deleted = false;
175
+ result.errorMessage = __getSPRestErrorData(e).message;
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ export async function RecycleListItem(siteUrl: string, listIdOrTitle: string, itemId: number | string): Promise<{ recycled: boolean; errorMessage?: string; }> {
182
+ siteUrl = GetSiteUrl(siteUrl);
183
+
184
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/recycle()`;
185
+
186
+ let result: { recycled: boolean; errorMessage?: string; } = { recycled: true };
187
+ try {
188
+ await GetJson<{ d: { Recycle: string; }; }>(url, null, { method: "POST", spWebUrl: siteUrl });
189
+ //value.d.Recycle will hold guide reference id
190
+ } catch (e) {
191
+ result.recycled = false;
192
+ result.errorMessage = __getSPRestErrorData(e).message;
193
+ }
194
+
195
+ return result;
196
+ }
197
+
198
+ export async function GetListItemAttachments(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<IAttachmentInfo[]> {
199
+ siteUrl = GetSiteUrl(siteUrl);
200
+
201
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles`;
202
+
203
+ try {
204
+ let result = await GetJson<{ d: { results: IAttachmentInfo[]; }; }>(url, null, { includeDigestInGet: true });
205
+ let attachmentFiles = result.d && result.d.results ? result.d.results : [];
206
+ return attachmentFiles;
207
+ } catch (e) {
208
+ }
209
+ return [];
210
+ }
211
+
212
+ export async function GetListItemsAttachments(siteUrl: string, listIdOrTitle: string, itemIds: number[]): Promise<{ Id: number, AttachmentFiles: IAttachmentInfo[] }[]> {
213
+ siteUrl = GetSiteUrl(siteUrl);
214
+ let chunks = chunkArray(itemIds, 30);
215
+ let select = `$select=ID,AttachmentFiles`;
216
+ let expand = `$expand=AttachmentFiles`;
217
+ let baseUrl = GetListRestUrl(siteUrl, listIdOrTitle) + `/items`;
218
+ let promises = chunks.map((chunk) => {
219
+ return () => {
220
+ let filter = `$filter=${chunk.map(i => `ID eq ${i}`).join(" or ")}`;
221
+ let url = `${baseUrl}?${select}&${filter}&${expand}`
222
+ return GetJson<{ value: { Id: number, AttachmentFiles: IAttachmentInfo[] } }>(url, null, { includeDigestInGet: true, jsonMetadata: jsonTypes.nometadata });
223
+ };
224
+ });
225
+ try {
226
+ let result = await promiseNParallel(promises, 5);
227
+ return result && result.length > 0 ? result.map(v => v.value) : [];
228
+ } catch {
229
+
230
+ }
231
+ return [];
232
+ }
233
+
234
+ export async function AddAttachment(siteUrl: string, listIdOrTitle: string, itemId: number, filename: string, buffer: ArrayBuffer) {
235
+ siteUrl = GetSiteUrl(siteUrl);
236
+
237
+ //Issue 999
238
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles/add(FileName='${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`;
239
+
240
+ try {
241
+ let result = await GetJson<{ d: IAttachmentInfo; }>(url, buffer, { includeDigestInPost: true, method: "POST" });
242
+ let attachmentFile = result && result.d;
243
+ return attachmentFile;
244
+ } catch (e) {
245
+ }
246
+ return null;
247
+ }
248
+
249
+ export async function DeleteAttachment(siteUrl: string, listIdOrTitle: string, itemId: number, filename: string) {
250
+ siteUrl = GetSiteUrl(siteUrl);
251
+
252
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles('${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`;
253
+
254
+ try {
255
+ let result = await GetJson<{ d: IAttachmentInfo; }>(url, null, { includeDigestInGet: true, includeDigestInPost: true, xHttpMethod: "DELETE" });
256
+ let attachmentFile = result && result.d;
257
+ return attachmentFile;
258
+ } catch (e) {
259
+ }
260
+ return null;
261
+ }
262
+
263
+ //** Update value of taxonomy multi-value field. See issue 7585 for more info */
264
+ export async function UpdateMultiTaxonomyValue(siteUrl: string, listIdOrTitle: string, itemId: number,
265
+ updates: IDictionary<TaxonomyValueType[]>) {
266
+
267
+ let fields = updates && Object.keys(updates) || [];
268
+
269
+ if (isNullOrEmptyArray(fields)) return [];
270
+
271
+ siteUrl = GetSiteUrl(siteUrl);
272
+
273
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`;
274
+
275
+ try {
276
+ let result = await GetJson<{
277
+ d: {
278
+ ValidateUpdateListItem: {
279
+ results: {
280
+ ErrorCode: number;
281
+ ErrorMEssage?: string;
282
+ FieldName: string;
283
+ FieldValue: string;
284
+ HasException: boolean;
285
+ ItemId: number;
286
+ }[];
287
+ };
288
+ };
289
+ }>(url, JSON.stringify({
290
+ bNewDocumentUpdate: false,
291
+ checkInComment: null,
292
+ formValues: fields.map(field => ({
293
+ ErrorMessage: null,
294
+ FieldName: field,
295
+ FieldValue: updates[field].map(v => `${v.Label}|${v.TermGuid};`).join(''),
296
+ HasException: false
297
+ }))
298
+ }), { includeDigestInPost: true, method: "POST" });
299
+ return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || [];
300
+ } catch (e) {
301
+ logger.error(`Error updating UpdateMultiTaxonomyValue ${e}`);
302
+ }
303
+ return fields.map(f => ({ field: f, error: 'Unspecified update error' }));
304
+ }
305
+
306
+ export async function AddItem(siteUrl: string, listIdOrTitle: string, fieldValues: IDictionary<any>) {
307
+ //we must force creating even if no values, otherwise the item won't be created at all.
308
+ return UpdateItem(siteUrl, listIdOrTitle, null, fieldValues, { updateIfNoFields: true });
309
+ }
310
+ export interface UpdateItemType {
311
+ updateProps: {
312
+ success: boolean;
313
+ itemId: number;
314
+ errorMessage?: undefined;
315
+ } | {
316
+ success: boolean;
317
+ errorMessage: string;
318
+ itemId: number;
319
+ }
320
+ }
321
+ export async function UpdateItem(siteUrl: string, listIdOrTitle: string, itemId: number, fieldValues: IDictionary<any>, options?: { updateIfNoFields?: boolean; }) {
322
+ var success = false;
323
+ var error: string = null;
324
+ try {
325
+ siteUrl = GetSiteUrl(siteUrl);
326
+
327
+ let isNewItem = itemId > 0 ? false : true;
328
+ let listInfo = await GetList(siteUrl, listIdOrTitle);
329
+ let fields = await GetListFieldsAsHash(siteUrl, listIdOrTitle);
330
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items${isNewItem ? '' : `(${itemId})`}`;
331
+
332
+ var itemUpdateInfo: { [key: string]: any; } = {
333
+ '__metadata': { 'type': `SP.Data.${listInfo.EntityTypeName}Item` }
334
+ };
335
+ let hasUpdates = false;
336
+ Object.keys(fieldValues).forEach(updateField => {
337
+ let listField = fields[updateField];
338
+ if (listField)//make sure this field exists on the list
339
+ {
340
+ //todo: we might want to get the value first, make sure it is formatted correctly for the field type.
341
+ itemUpdateInfo[getFieldNameForUpdate(listField)] = fieldValues[updateField];
342
+ hasUpdates = true;
343
+ }
344
+ });
345
+
346
+ if (!hasUpdates) {
347
+ let forceUpdate = options && options.updateIfNoFields;
348
+ if (!forceUpdate)
349
+ return { success: true, itemId: itemId };
350
+ }
351
+
352
+ var xHttpMethod: "MERGE" = isNewItem ? null : "MERGE";
353
+
354
+ try {
355
+ let result = await GetJson<{ d: { Id: number; }; }>(url, JSON.stringify(itemUpdateInfo), { method: "POST", xHttpMethod: xHttpMethod });
356
+ if (result)
357
+ if (isNewItem)
358
+ itemId = result.d.Id;// update item will not return data. only new item will.
359
+ success = true;
360
+ } catch (e) {
361
+ error = __getSPRestErrorData(e).message;
362
+ }
363
+ } catch (e) {
364
+ logger.group(() => logger.error(e), 'Update item failed');
365
+ }
366
+ return { success: success, errorMessage: error, itemId: itemId };
367
+ }
368
+ /** Get a sharepoint field value as text, from a rest item */
369
+ export function GetSPRestItemValueAsText(item: IRestItem, field: IFieldInfoEX): string {
370
+
371
+ var otherName = field.InternalName.replace(/_/g, "_x005f_");//sometimes _ gets replaced with _x005f_
372
+ if (!isNullOrUndefined(item.FieldValuesAsText)) {
373
+ if (hasOwnProperty(item.FieldValuesAsText, field.InternalName))
374
+ return item.FieldValuesAsText[field.InternalName];
375
+ if (hasOwnProperty(item.FieldValuesAsText, otherName))
376
+ return item.FieldValuesAsText[otherName];
377
+ }
378
+
379
+ return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field).join(', ');
380
+ }
381
+ /** Get a sharepoint field value as text array, from a rest item */
382
+ export function GetSPRestItemValueAsTextArray(item: IRestItem, field: IFieldInfoEX): string[] {
383
+ //get value as text first
384
+ let valueAsText = null;
385
+ var otherName = field.InternalName.replace(/_/g, "_x005f_");//sometimes _ gets replaced with _x005f_
386
+ if (!isNullOrUndefined(item.FieldValuesAsText)) {
387
+ if (hasOwnProperty(item.FieldValuesAsText, field.InternalName))
388
+ valueAsText = item.FieldValuesAsText[field.InternalName];
389
+ if (hasOwnProperty(item.FieldValuesAsText, otherName))
390
+ valueAsText = item.FieldValuesAsText[otherName];
391
+ }
392
+ if (!isNullOrEmptyString(valueAsText) && valueAsText.indexOf(',') < 0)//not empty, and we do not suspect a multi-value field
393
+ return [valueAsText];
394
+
395
+ return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field);
396
+ }
397
+ /** prefer to use GetSPRestValueAsText instead */
398
+ export function GetSPFieldValueAsText(value: any, field: IFieldInfoEX): string[] {
399
+ let locales = LocaleKnownScript.loadSync();
400
+ let culture = locales.GetCurrentCulture();
401
+
402
+ let rawValues: (string | number | boolean | Date | TaxonomyValueType | TaxonomyValueType[] | { Id: string | number; Title: string; })[] =
403
+ isNullOrEmptyString(value)
404
+ ? []
405
+ : Array.isArray(value)
406
+ ? value//value.raw is an array
407
+ : [value];//value.raw is not an array - wrap it.
408
+
409
+ let isLookup = field.TypeAsString === "Lookup" || field.TypeAsString === "LookupMulti";
410
+ let isUser = field.TypeAsString === "User" || field.TypeAsString === "UserMulti";
411
+ let isCounter = field.TypeAsString === "Counter" || field.TypeAsString === "Integer";
412
+ let isTaxonomy = field.TypeAsString === "TaxonomyFieldType" || field.TypeAsString === "TaxonomyFieldTypeMulti";
413
+ if (field.TypeAsString === "DateTime") {
414
+ //Issue 8190 - date field might come as string
415
+ rawValues = rawValues.map(v => isDate(v) ? v : new Date(v as string));
416
+ }
417
+ else if (isUser || isLookup) {
418
+ rawValues = rawValues.map(v => isNumber(v) ? v : !isNullOrEmptyString(v && (v as any).Title) ? (v as any).Title : isNumber(v && (v as any).Id) ? (v as any).Id : null);
419
+ }
420
+
421
+ let textResults: string[] = [];
422
+ if (isNotEmptyArray(rawValues)) {
423
+ rawValues.forEach(raw => {
424
+ if (isNullOrEmptyString(raw)) {/** skip */ }
425
+ else if (isNumber(raw))
426
+ if (isUser) {
427
+ //todo - try not sync...
428
+ try {
429
+ let userInfo = GetUserSync(_spPageContextInfo.siteServerRelativeUrl, raw);
430
+ textResults.push(userInfo.Title);
431
+ } catch (e) {
432
+ textResults.push(`${raw}`);
433
+ }
434
+ }
435
+ else if (isLookup) {
436
+ //todo - not supported
437
+ try {
438
+ textResults.push(`Lookup #${raw}`);
439
+ }
440
+ catch (e) {
441
+ textResults.push(`${raw}`);
442
+ }
443
+ } else if (isCounter) {
444
+ textResults.push(raw.toString());
445
+ } else {
446
+ textResults.push(locales.NumberToString(raw, culture, {
447
+ isCurrency: isNumber((field as IFieldCurrencyInfo).CurrencyLocaleId),
448
+ isPercent: (field as IFieldNumberInfo).ShowAsPercentage
449
+ }));
450
+ }
451
+ else if (isTaxonomy) {
452
+ if (isNotEmptyArray(raw)) {
453
+ textResults.push(raw.map(t => `${t.Label}|${t.TermGuid}`).join(';'));
454
+ } else if (isObject(raw) && raw !== null && 'Label' in raw) {
455
+ textResults.push(raw.Label || '');
456
+ }
457
+ }
458
+ else if (isString(raw))
459
+ textResults.push(raw);
460
+ else if (isBoolean(raw)) {
461
+ textResults.push(raw ? "Yes" : "No");
462
+ }
463
+ else if (isDate(raw)) {
464
+ textResults.push(locales.DateToString(raw, culture, {
465
+ includeDate: true,
466
+ includeTime: (field as IFieldDateTimeInfo).DisplayFormat === DateTimeFieldFormatType.DateTime
467
+ }));
468
+ }
469
+ });
470
+ }
471
+ return textResults;
472
+ }
473
+
474
+ /** set an existing item system info: author, editor, created and modified dates */
475
+ export async function SetItemCreatedModifiedInfo(siteUrl: string, listIdOrTitle: string, itemId: number,
476
+ updates: { Created?: string | Date; Modified?: string | Date; AuthorId?: number; EditorId?: number; }) {
477
+
478
+ let updateValues: IDictionary<string> = {};
479
+
480
+ let fields = updates && Object.keys(updates) || [];
481
+ if (!isNullOrEmptyString(updates.Created))//date must be yyyy-MM-dd hh:mm:ss
482
+ updateValues.Created = (isString(updates.Created) ? new Date(updates.Created) : updates.Created).toISOString().replace('T', ' ').split('.')[0];
483
+ if (!isNullOrEmptyString(updates.Modified))
484
+ updateValues.Modified = (isString(updates.Modified) ? new Date(updates.Modified) : updates.Modified).toISOString().replace('T', ' ').split('.')[0];
485
+ if (updates.AuthorId > 0) {
486
+ let asUser = await GetUser(siteUrl, updates.AuthorId);
487
+ updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`;//[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}]
488
+ }
489
+ if (updates.EditorId > 0) {
490
+ let asUser = await GetUser(siteUrl, updates.EditorId);
491
+ updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`;//[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}]
492
+ }
493
+
494
+ if (isNullOrEmptyArray(fields)) return [];
495
+
496
+ siteUrl = GetSiteUrl(siteUrl);
497
+
498
+ let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`;
499
+
500
+ try {
501
+ let result = await GetJson<{
502
+ d: {
503
+ ValidateUpdateListItem: {
504
+ results: {
505
+ ErrorCode: number;
506
+ ErrorMEssage?: string;
507
+ FieldName: string;
508
+ FieldValue: string;
509
+ HasException: boolean;
510
+ ItemId: number;
511
+ }[];
512
+ };
513
+ };
514
+ }>(url, JSON.stringify({
515
+ formValues: fields.map(field => ({
516
+ FieldName: field,
517
+ FieldValue: updateValues[field]
518
+ }))
519
+ }), { method: "POST" });
520
+ return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || [];
521
+ } catch (e) {
522
+ logger.error(`Error updating values ${e}`);
523
+ }
524
+ return fields.map(f => ({ field: f, error: 'Unspecified update error' }));
525
+ }
526
+
527
+ export async function ListItemHasUniquePermissions(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<boolean> {
528
+ let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/?$select=hasuniqueroleassignments`;
529
+ let has = await GetJson<{ HasUniqueRoleAssignments: boolean }>(url, undefined, { allowCache: false, jsonMetadata: jsonTypes.nometadata });
530
+ return has.HasUniqueRoleAssignments === true;
531
+ }
532
+ export async function RestoreListItemPermissionInheritance(siteUrl: string, listIdOrTitle: string, itemId: number): Promise<void> {
533
+ let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/ResetRoleInheritance`;
534
+ await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
535
+ }
536
+ export async function BreakListItemPermissionInheritance(siteUrl: string, listIdOrTitle: string, itemId: number, clear = true): Promise<void> {
537
+ let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/breakroleinheritance(copyRoleAssignments=${clear ? 'false' : 'true'}, clearSubscopes=true)`;
538
+ await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
539
+ }
540
+ export async function AssignListItemPermission(siteUrl: string, listIdOrTitle: string, itemId: number, principalId: number, roleId: number) {
541
+ let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/addroleassignment(principalid=${principalId},roleDefId=${roleId})`;
542
+ await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
543
+ }
544
+ export async function RemoveListItemPermission(siteUrl: string, listIdOrTitle: string, itemId: number, principalId: number, roleId: number) {
545
+ let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/removeroleassignment(principalid=${principalId},roleDefId=${roleId})`;
546
+ await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl });
547
+ }