@kwiz/common 1.0.78 → 1.0.79

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 (113) hide show
  1. package/.github/workflows/npm-publish.yml +24 -0
  2. package/.madgerc +2 -2
  3. package/LICENSE +21 -21
  4. package/fix-folder-imports.js +26 -26
  5. package/lib/cjs/helpers/sharepoint.js +5 -1
  6. package/lib/cjs/helpers/sharepoint.js.map +1 -1
  7. package/lib/cjs/types/libs/msal.types.js +26 -26
  8. package/lib/cjs/utils/sharepoint.rest/list.js +1 -1
  9. package/lib/cjs/utils/sharepoint.rest/list.js.map +1 -1
  10. package/lib/cjs/utils/sharepoint.rest/user.js +11 -11
  11. package/lib/esm/helpers/sharepoint.js +3 -0
  12. package/lib/esm/helpers/sharepoint.js.map +1 -1
  13. package/lib/esm/types/libs/msal.types.js +26 -26
  14. package/lib/esm/utils/sharepoint.rest/list.js +2 -2
  15. package/lib/esm/utils/sharepoint.rest/list.js.map +1 -1
  16. package/lib/esm/utils/sharepoint.rest/user.js +11 -11
  17. package/lib/types/helpers/sharepoint.d.ts +1 -0
  18. package/package.json +77 -77
  19. package/readme.md +17 -17
  20. package/src/_dependencies.ts +12 -12
  21. package/src/config.ts +17 -17
  22. package/src/helpers/Guid.ts +181 -181
  23. package/src/helpers/base64.ts +173 -173
  24. package/src/helpers/browser.test.js +13 -13
  25. package/src/helpers/browser.ts +1348 -1348
  26. package/src/helpers/browserinfo.ts +292 -292
  27. package/src/helpers/collections.base.test.js +25 -25
  28. package/src/helpers/collections.base.ts +437 -437
  29. package/src/helpers/collections.ts +107 -107
  30. package/src/helpers/color.ts +54 -54
  31. package/src/helpers/cookies.ts +59 -59
  32. package/src/helpers/date.test.js +119 -119
  33. package/src/helpers/date.ts +188 -188
  34. package/src/helpers/debug.ts +186 -186
  35. package/src/helpers/emails.ts +6 -6
  36. package/src/helpers/eval.ts +5 -5
  37. package/src/helpers/file.test.js +50 -50
  38. package/src/helpers/file.ts +58 -58
  39. package/src/helpers/flatted.ts +149 -149
  40. package/src/helpers/functions.ts +16 -16
  41. package/src/helpers/graph/calendar.types.ts +10 -10
  42. package/src/helpers/http.ts +69 -69
  43. package/src/helpers/images.ts +22 -22
  44. package/src/helpers/json.ts +38 -38
  45. package/src/helpers/md5.ts +189 -189
  46. package/src/helpers/objects.test.js +33 -33
  47. package/src/helpers/objects.ts +270 -270
  48. package/src/helpers/promises.test.js +37 -37
  49. package/src/helpers/promises.ts +165 -165
  50. package/src/helpers/random.ts +27 -27
  51. package/src/helpers/scheduler/scheduler.test.js +103 -103
  52. package/src/helpers/scheduler/scheduler.ts +131 -131
  53. package/src/helpers/sharepoint.ts +776 -772
  54. package/src/helpers/strings.test.js +101 -101
  55. package/src/helpers/strings.ts +317 -317
  56. package/src/helpers/typecheckers.test.js +34 -34
  57. package/src/helpers/typecheckers.ts +262 -262
  58. package/src/helpers/url.test.js +43 -43
  59. package/src/helpers/url.ts +207 -207
  60. package/src/helpers/urlhelper.ts +111 -111
  61. package/src/index.ts +6 -6
  62. package/src/types/auth.ts +54 -54
  63. package/src/types/common.types.ts +15 -15
  64. package/src/types/flatted.types.ts +59 -59
  65. package/src/types/globals.types.ts +6 -6
  66. package/src/types/graph/calendar.types.ts +80 -80
  67. package/src/types/knownscript.types.ts +18 -18
  68. package/src/types/libs/datajs.types.ts +28 -28
  69. package/src/types/libs/ics.types.ts +30 -30
  70. package/src/types/libs/msal.types.ts +49 -49
  71. package/src/types/locales.ts +124 -124
  72. package/src/types/localstoragecache.types.ts +8 -8
  73. package/src/types/location.types.ts +27 -27
  74. package/src/types/moment.ts +11 -11
  75. package/src/types/regex.types.ts +16 -16
  76. package/src/types/rest.types.ts +95 -95
  77. package/src/types/sharepoint.types.ts +1465 -1465
  78. package/src/types/sharepoint.utils.types.ts +287 -287
  79. package/src/utils/auth/common.ts +74 -74
  80. package/src/utils/auth/discovery.test.js +12 -12
  81. package/src/utils/auth/discovery.ts +132 -132
  82. package/src/utils/base64.ts +27 -27
  83. package/src/utils/consolelogger.ts +320 -320
  84. package/src/utils/date.ts +35 -35
  85. package/src/utils/emails.ts +24 -24
  86. package/src/utils/knownscript.ts +286 -286
  87. package/src/utils/localstoragecache.ts +441 -441
  88. package/src/utils/rest.ts +501 -501
  89. package/src/utils/script.ts +170 -170
  90. package/src/utils/sharepoint.rest/common.ts +154 -154
  91. package/src/utils/sharepoint.rest/date.ts +62 -62
  92. package/src/utils/sharepoint.rest/file.folder.ts +598 -598
  93. package/src/utils/sharepoint.rest/item.ts +547 -547
  94. package/src/utils/sharepoint.rest/list.ts +1388 -1388
  95. package/src/utils/sharepoint.rest/listutils/GetListItemsByCaml.ts +774 -774
  96. package/src/utils/sharepoint.rest/listutils/GetListItemsById.ts +275 -275
  97. package/src/utils/sharepoint.rest/listutils/common.ts +206 -206
  98. package/src/utils/sharepoint.rest/location.ts +141 -141
  99. package/src/utils/sharepoint.rest/navigation-links.ts +86 -86
  100. package/src/utils/sharepoint.rest/user-search.ts +252 -252
  101. package/src/utils/sharepoint.rest/user.ts +491 -491
  102. package/src/utils/sharepoint.rest/web.ts +1384 -1384
  103. package/src/utils/sod.ts +194 -194
  104. package/lib/cjs/helpers/_dependencies.js +0 -21
  105. package/lib/cjs/helpers/_dependencies.js.map +0 -1
  106. package/lib/cjs/utils/_dependencies.js +0 -24
  107. package/lib/cjs/utils/_dependencies.js.map +0 -1
  108. package/lib/esm/helpers/_dependencies.js +0 -3
  109. package/lib/esm/helpers/_dependencies.js.map +0 -1
  110. package/lib/esm/utils/_dependencies.js +0 -4
  111. package/lib/esm/utils/_dependencies.js.map +0 -1
  112. package/lib/types/helpers/_dependencies.d.ts +0 -2
  113. package/lib/types/utils/_dependencies.d.ts +0 -3
@@ -1,775 +1,775 @@
1
- import { IsLocalDev } from "../../../_dependencies";
2
- import { chunkArray, firstIndexOf, firstOrNull, makeUniqueArray, toHash } from "../../../helpers/collections.base";
3
- import { EnsureViewFields, GetOrderByFromCaml, RemoveOrderByFromCaml, getFieldOutputType } from "../../../helpers/sharepoint";
4
- import { isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumeric } 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 { IFieldInfoEX } from "../../../types/sharepoint.types";
9
- import { GeListItemsFoldersBehaviour, IRestItem } from "../../../types/sharepoint.utils.types";
10
- import { ConsoleLogger } from "../../consolelogger";
11
- import { GetJson } from "../../rest";
12
- import { GetSiteUrl, __getSPRestErrorData } from "../common";
13
- import { GetListFields, GetListRestUrl } from "../list";
14
- import { SPServerLocalTimeToUTCSync } from "../web";
15
- import { GetItemsById } from "./GetListItemsById";
16
- import { SkipFields, __fixGetListItemsResults } from "./common";
17
-
18
- const logger = ConsoleLogger.get("sharepoint.rest/list/GetListItemsByCaml");
19
-
20
- interface ICamlOptions {
21
- /** Optional, default: 1000. 0: get all items. */
22
- rowLimit?: number;
23
- /** Id, Title, Modified, FileLeafRef, FileDirRef, FileRef, FileSystemObjectType */
24
- columns: string[];
25
- foldersBehaviour?: GeListItemsFoldersBehaviour;
26
- /** set query to get items from this folder only */
27
- FolderServerRelativeUrl?: string;
28
- refreshCache?: boolean;
29
- }
30
-
31
- //Lookup threshold limit is 12 but we use a smaller number here to ensure there are no issues
32
- const lookupOrUserFieldLimit = 11;
33
- /** returns the items or NULL, never errors out since it is used in aggregator.
34
- * set throwErrors = true if you want errors to be thrown instead of returning null.
35
- * camlQuery: one of:
36
- * - view.ViewQuery
37
- * - wrapped view.ViewQuery in a <View>: "&lt;View Scope='RecursiveAll'>&lt;Query>&lt;/Query>&lt;RowLimit>1000&lt;/RowLimit>&lt;/View>" or send rowLimit in options.
38
- */
39
- export async function GetListItemsByCaml(siteUrl: string, listIdOrTitle: string, camlQuery: string, options: ICamlOptions): Promise<IRestItem[]> {
40
- siteUrl = GetSiteUrl(siteUrl);
41
-
42
- if (!camlQuery.toLowerCase().startsWith("<view")) {
43
- camlQuery = `<View Scope='RecursiveAll'><Query>${camlQuery}</Query></View>`;
44
- }
45
-
46
- let xmlParser = new DOMParser();
47
- let xmlDoc = xmlParser.parseFromString(camlQuery, "text/xml");
48
- let viewNode = xmlDoc.querySelector("View, view");
49
- let queryNode = viewNode && viewNode.querySelector("Query, query");
50
- let rowLimitNode = viewNode && viewNode.querySelector("RowLimit, rowlimit");
51
- let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
52
-
53
- let parseRowLimitNodeResult = _parseRowLimitNode(rowLimitNode, viewNode, options.rowLimit);
54
- if (!isNullOrUndefined(parseRowLimitNodeResult)) {
55
- options.rowLimit = parseRowLimitNodeResult.rowLimit;
56
- camlQuery = parseRowLimitNodeResult.camlQuery;
57
- }
58
-
59
- let removeSingleAndConditionsResult = _removeSingleAndConditions(viewNode, queryNode);
60
- if (!isNullOrUndefined(removeSingleAndConditionsResult)) {
61
- camlQuery = removeSingleAndConditionsResult.camlQuery;
62
- }
63
-
64
- let getAllItems = isNullOrEmptyString(options.rowLimit) || options.rowLimit < 1;
65
- let totalItemsInList = 99999;
66
-
67
- let maxBatchSize = 5000;
68
- let batchSize = options.rowLimit > 0 && options.rowLimit < maxBatchSize ? options.rowLimit : maxBatchSize;
69
- let totalNumberOfItemsToGet = getAllItems ? totalItemsInList : options.rowLimit > maxBatchSize ? options.rowLimit : batchSize;
70
-
71
- let requestUrl = `${GetListRestUrl(siteUrl, listIdOrTitle)}/GetItems?$expand=FieldValuesAsText`;
72
-
73
- let allListFieldsHash = toHash(await GetListFields(siteUrl, listIdOrTitle), f => f.InternalName);
74
-
75
- let expandFields: string[] = [];
76
-
77
- let orderByStatement = GetOrderByFromCaml(camlQuery);
78
-
79
- if (isNullOrUndefined(options.columns)) {
80
- options.columns = [];
81
- }
82
-
83
- let columns = options.columns;
84
- columns = _ensureOrderByColumns(orderByStatement, columns);
85
- columns = _ensureViewFields(viewFieldsNode, columns, allListFieldsHash);
86
- columns = _normalizeColumns(columns, allListFieldsHash);
87
-
88
- if (columns.length > options.columns.length) {
89
- logger.warn(`added ${columns.length - options.columns.length} to query`);
90
- }
91
-
92
- options.columns = columns;
93
-
94
- // Store the result of processing the request
95
- let rtrnProcessRequestResult: { items: IRestItem[]; postProcessOrderBy: boolean; needContentTypes: boolean };
96
-
97
- let lookupOrUserFieldsInColumns = options.columns.filter((columnName) => {
98
- let field = allListFieldsHash[columnName];
99
- return _isLookupOrUserField(field);
100
- });
101
-
102
- let isThrottled = lookupOrUserFieldsInColumns.length >= lookupOrUserFieldLimit;
103
-
104
- try {
105
- if (isThrottled) {
106
- //ISSUE: 1565
107
- rtrnProcessRequestResult = await _processLookupThresholdCamlRequest(
108
- orderByStatement,
109
- allListFieldsHash,
110
- options,
111
- camlQuery,
112
- requestUrl,
113
- expandFields,
114
- siteUrl,
115
- totalNumberOfItemsToGet,
116
- batchSize);
117
- } else {
118
- rtrnProcessRequestResult = await _processNormalCamlRequest(
119
- orderByStatement,
120
- allListFieldsHash,
121
- options,
122
- camlQuery,
123
- requestUrl,
124
- expandFields,
125
- siteUrl,
126
- totalNumberOfItemsToGet,
127
- batchSize);
128
- }
129
-
130
- const { items, needContentTypes, postProcessOrderBy } = rtrnProcessRequestResult
131
-
132
- let itemsResult = __fixGetListItemsResults(siteUrl, listIdOrTitle, items, options.foldersBehaviour);
133
-
134
- let itemsWithOutContentType: number[] = [];
135
- if (needContentTypes) {
136
- itemsResult.forEach((item) => {
137
- if (isNullOrUndefined(item["ContentType"])) {
138
- itemsWithOutContentType.push(item.Id);
139
- } else if (!isNullOrUndefined(item["ContentType"].Name)) {
140
- item["ContentType"] = item["ContentType"].Name;
141
- }
142
- });
143
- }
144
-
145
- if (itemsWithOutContentType.length > 0) {
146
- logger.time("Getting content types");
147
- //Issue 1465 content types no longer come back from get items request...
148
- //Make a separate request to get this info
149
- let ctypes = (await GetItemsById(siteUrl, listIdOrTitle, itemsWithOutContentType, {
150
- expand: ['ContentType/Name'],
151
- select: ['ContentType/Name', 'Id'],
152
- jsonMetadata: jsonTypes.nometadata
153
- })) as any as { Id: string, ContentType: { Name: string } }[];
154
-
155
- itemsResult.forEach(item => {
156
- if (!isNullOrUndefined(ctypes[item.Id])) {
157
- item["ContentType"] = ctypes[item.Id].ContentType.Name
158
- }
159
- });
160
- logger.timeEnd("Getting content types");
161
- }
162
-
163
- if (postProcessOrderBy) {
164
- //re-apply sort
165
- if (IsLocalDev) {
166
- logger.table(itemsResult.map(i => {
167
- let row = {
168
- Id: i.Id,
169
- Title: i.Title
170
- };
171
- orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
172
- return row;
173
- }), "before sort", true);
174
- }
175
- itemsResult.sort((a, b) => {
176
- for (let i = 0; i < orderByStatement.length; i++) {
177
- let ob = orderByStatement[i];
178
- let v1 = a[ob.Name];
179
- let v2 = b[ob.Name];
180
- if (v1 === v2) {
181
- //these are equal - continue to second sort statement
182
- }
183
- else {
184
- if (ob.IsAscending)
185
- return (v1 > v2) ? 1 : -1;
186
- else
187
- return (v2 > v1) ? 1 : -1;
188
- }
189
- }
190
- return 0;
191
- });
192
-
193
- if (IsLocalDev) {
194
- logger.table(itemsResult.map(i => {
195
- let row = {
196
- Id: i.Id,
197
- Title: i.Title
198
- };
199
- orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
200
- return row;
201
- }), "after sort", true);
202
- }
203
-
204
- }
205
-
206
- return itemsResult;
207
- } catch (ex) {
208
- console.log(`isThrottled: ${isThrottled}`);
209
- }
210
-
211
- return null;
212
- }
213
-
214
- // (window as any).runtTest = true;
215
- // window.setTimeout(() => {
216
- // if ((window as any).runtTest == false) return;
217
- // (window as any).runtTest = false;
218
- // GetListItemsByCaml(null, "aec7756b-daa0-4da1-88ba-c66cb572e816", "", {
219
- // columns: [],
220
- // refreshCache: true
221
- // }).then((r) => {
222
-
223
- // });
224
- // }, 500);
225
-
226
- //ISSUE: 1565
227
- async function _processLookupThresholdCamlRequest(
228
- orderByStatement: { Name: string; IsAscending: boolean; }[],
229
- allListFieldsHash: IDictionary<IFieldInfoEX>,
230
- options: ICamlOptions,
231
- camlQuery: string,
232
- requestUrl: string,
233
- expandFields: string[],
234
- siteUrl: string,
235
- totalNumberOfItemsToGet: number,
236
- batchSize: number) {
237
- let rtrnProcessRequestResult = {
238
- items: [],
239
- postProcessOrderBy: false,
240
- needContentTypes: false
241
- };
242
-
243
- let orderByFields = orderByStatement.map((orderByField) => {
244
- let field = allListFieldsHash[orderByField.Name];
245
- return field;
246
- });
247
-
248
- let lookupOrUserFieldsInOrderBy = orderByFields.filter((orderByField) => {
249
- return _isLookupOrUserField(orderByField);
250
- });
251
-
252
- let lookupOrUserFieldsToChunk = options.columns.filter((columnName) => {
253
- let field = allListFieldsHash[columnName];
254
- if (_isLookupOrUserField(field)) {
255
- let col = firstOrNull(lookupOrUserFieldsInOrderBy, (orderByField) => {
256
- return field.InternalName !== orderByField.InternalName;
257
- });
258
-
259
- return col === null;
260
- }
261
- return false;
262
- });
263
-
264
- let otherFieldNames = options.columns.filter((columnName) => {
265
- let field = allListFieldsHash[columnName];
266
- return !isNullOrUndefined(field) && !_isLookupOrUserField(field);
267
- });
268
-
269
- //The number of lookup columns in each request is based on the lookupOrUserFieldLimit.
270
- //Lookup fields in the order by statement must be sent with each request.
271
- //For example, we have 20 lookup columns and 3 of them are in the order by statement.
272
- //The 3 order by lookup columns are sent with each request. The remaining lookups are split into chunks.
273
- //The request will split into 3 requests
274
- //The first request will have 11 lookup columns (8 standard + 3 order by lookup columns)
275
- //The second request will have 11 lookup columns (8 standard + 3 order by lookup columns)
276
- //The third request will have 4 lookup columns (1 standard + 3 order by lookup columns)
277
- let requestChunks = chunkArray(lookupOrUserFieldsToChunk, lookupOrUserFieldLimit - lookupOrUserFieldsInOrderBy.length);
278
- let otherFieldsChunkSize = Math.ceil(otherFieldNames.length / requestChunks.length);
279
- let otherFieldsChunks = chunkArray(otherFieldNames, otherFieldsChunkSize);
280
-
281
- requestChunks.forEach((chunk, index) => {
282
- //Add all order by fields to each request, this will include the lookup
283
- //fields from the order by statement that we left room for previously
284
- requestChunks[index] = chunk.concat(orderByFields.map((orderByField) => {
285
- return orderByField.InternalName;
286
- }));
287
-
288
- //Add the other fields but split them across the requests so we don't request duplicate data
289
- if (otherFieldsChunks[index]) {
290
- requestChunks[index] = chunk.concat(otherFieldsChunks[index]);
291
- }
292
- });
293
-
294
- //requestChunks should now have about the same number of fields in each chunk and each
295
- //chunk will have all the order by fields.
296
- let queries = requestChunks.map((requestChunk) => {
297
- let camlQueryClone = camlQuery;
298
-
299
- let processColumnsResult = _processColumns(requestChunk, expandFields, allListFieldsHash);
300
-
301
- //Id field must be included in oder to merge the items correctly
302
- let viewFields = processColumnsResult.viewFields;
303
- if (viewFields.length && !viewFields.includes("Id")) {
304
- viewFields.push("Id");
305
- }
306
-
307
- let selectFields = processColumnsResult.selectFields
308
- if (selectFields.length && !selectFields.includes("Id")) {
309
- selectFields.push("Id");
310
- }
311
-
312
- rtrnProcessRequestResult.needContentTypes = rtrnProcessRequestResult.needContentTypes || processColumnsResult.needContentTypes;
313
-
314
- expandFields = processColumnsResult.expandFields;
315
-
316
- if (isNotEmptyArray(viewFields)) {
317
- camlQueryClone = EnsureViewFields(camlQueryClone, viewFields, true);
318
- }
319
-
320
- // if (isDebug()) {
321
- // let lfields = viewFields.filter((fieldName) => {
322
- // let field = allListFieldsHash[fieldName];
323
- // return _isLookupOrUserField(field);
324
- // });
325
-
326
- // let xmlDoc = new DOMParser().parseFromString(camlQueryClone, "text/xml");
327
- // let viewNode = xmlDoc.querySelector("View, view");
328
- // let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
329
-
330
- // let viewFields2 = Array.from(viewFieldsNode.children).map((viewFieldNode) => {
331
- // let name = viewFieldNode.getAttribute("Name") || viewFieldNode.getAttribute("name");
332
- // return name;
333
- // });
334
-
335
- // let lfields2 = viewFields2.filter((fieldName) => {
336
- // let field = allListFieldsHash[fieldName];
337
- // return _isLookupOrUserField(field);
338
- // });
339
-
340
- // if (lfields2.length !== lfields.length) {
341
- // logger.warn("Lookup fields in caml query do not match look up fields in view fields.");
342
- // logger.warn(`Lookup fields in caml query: ${lfields2}`)
343
- // logger.warn(`Lookup fields in in view fields: ${lfields}`)
344
- // }
345
- // }
346
-
347
- // Prepare the REST URL with select fields
348
- let restUrlWithSelect = requestUrl;
349
- if (expandFields.length > 0) {
350
- restUrlWithSelect += ',' + expandFields.join(',');
351
- }
352
- // Include the lookup user select fields with the other select fields
353
- restUrlWithSelect += '&$select=' + selectFields.join(',');
354
- return { camlQueryClone, restUrlWithSelect, viewFields };
355
- });
356
-
357
- let mergedItems: IRestItem[] = null;
358
- for (let i = 0; i < queries.length; i++) {
359
- const { camlQueryClone, restUrlWithSelect, viewFields } = queries[i];
360
-
361
- let loopResult = await processRequestResult(requestUrl,
362
- restUrlWithSelect,
363
- camlQueryClone,
364
- { ...options, columns: viewFields },
365
- siteUrl,
366
- totalNumberOfItemsToGet,
367
- batchSize,
368
- orderByStatement);
369
-
370
- if (isNullOrUndefined(mergedItems)) {
371
- mergedItems = loopResult.items;
372
- } else {
373
- for (let restItemIndex = 0; restItemIndex < loopResult.items.length; restItemIndex++) {
374
- //The item chunks (loopResult) should be in the same order as mergedItems because each request should have the
375
- //same number of items returned in the same order. The only difference between requests is which fields are present.
376
- //So request 1 will have the 1st set of fields, request 2 will have the 2nd set. etc.
377
- //This means that the restItemIndex should be the same as existingItemIndex for each iteration. But, we do these extra
378
- //checks just in case.
379
- let restItem = loopResult.items[restItemIndex];
380
- let existingItem = mergedItems[restItemIndex];
381
- let existingItemIndex = restItemIndex;
382
- if (isNullOrUndefined(existingItem) || existingItem.Id !== restItem.Id) {
383
- existingItemIndex = firstIndexOf(mergedItems, (mergedItem) => {
384
- return mergedItem.Id === restItem.Id;
385
- });
386
- }
387
-
388
- if (existingItemIndex === -1) {
389
- //We shouldn't get here. Each chunk should have the same items in the same order.
390
- logger.warn("_processLookupThresholdCamlRequest results are out of sync");
391
- mergedItems.push(restItem);
392
- } else {
393
- let existingItem = mergedItems[existingItemIndex];
394
- let FieldValuesAsText = {
395
- ...(existingItem.FieldValuesAsText || {}),
396
- ...(restItem.FieldValuesAsText || {})
397
- };
398
- let FieldValuesForEdit = {
399
- ...(existingItem.FieldValuesForEdit || {}),
400
- ...(restItem.FieldValuesForEdit || {})
401
- };
402
- existingItem = { ...existingItem, ...restItem };
403
- existingItem.FieldValuesAsText = FieldValuesAsText;
404
- existingItem.FieldValuesForEdit = FieldValuesForEdit;
405
- mergedItems[existingItemIndex] = existingItem;
406
- }
407
- }
408
- }
409
-
410
- // only need to put this true if it happens once
411
- if (loopResult.postProcessOrderBy) {
412
- rtrnProcessRequestResult.postProcessOrderBy = true;
413
- }
414
- }
415
-
416
- rtrnProcessRequestResult.items = mergedItems;
417
-
418
- return rtrnProcessRequestResult;
419
- }
420
-
421
- async function _processNormalCamlRequest(orderByStatement: { Name: string; IsAscending: boolean; }[],
422
- allListFieldsHash: IDictionary<IFieldInfoEX>,
423
- options: ICamlOptions,
424
- camlQuery: string,
425
- requestUrl: string,
426
- expandFields: string[],
427
- siteUrl: string,
428
- totalNumberOfItemsToGet: number,
429
- batchSize: number) {
430
- let processColumnsResult = _processColumns(options.columns, expandFields, allListFieldsHash);
431
- let viewFields = processColumnsResult.viewFields;
432
- let selectFields = processColumnsResult.selectFields;
433
- expandFields = processColumnsResult.expandFields;
434
- let needContentTypes = processColumnsResult.needContentTypes;
435
-
436
- if (isNotEmptyArray(viewFields)) {
437
- camlQuery = EnsureViewFields(camlQuery, viewFields, true);
438
- }
439
-
440
- // Prepare the REST URL with select fields
441
- let restUrlWithSelect = requestUrl;
442
- if (expandFields.length > 0) {
443
- restUrlWithSelect += ',' + expandFields.join(',');
444
- }
445
- // Include the lookup user select fields with the other select fields
446
- restUrlWithSelect += '&$select=' + selectFields.join(',');
447
-
448
- // Process the request and get the result
449
- let result = await processRequestResult(requestUrl, restUrlWithSelect, camlQuery, options, siteUrl, totalNumberOfItemsToGet, batchSize, orderByStatement);
450
-
451
- return {
452
- ...result,
453
- needContentTypes
454
- };
455
- }
456
-
457
- function _ensureViewFields(viewFieldsNode: Element, columns: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
458
- //ISSUE: 1565
459
- //Cases
460
- //1. Empty view fields element so ALL columns will be requested. We must include all list fields in
461
- //columns parsing so that we get an accurate count of how many lookup/user fields there are
462
- //2. View fields element with a some field names that must also be included in the column parsing so we add them here.
463
- if (isNullOrUndefined(viewFieldsNode) && isNullOrEmptyArray(columns)) {
464
- Object.keys(allListFieldsHash).forEach((fieldName) => {
465
- let field = allListFieldsHash[fieldName];
466
- if (!isNullOrUndefined(field)
467
- && !field.Hidden
468
- && !SkipFields.includes(fieldName.toLowerCase())) {
469
- columns.push(field.InternalName);
470
- }
471
- });
472
- } else if (!isNullOrUndefined(viewFieldsNode)) {
473
- let fieldRefNodes = Array.from(viewFieldsNode.querySelectorAll("FieldRef"));
474
- if (isNotEmptyArray(fieldRefNodes)) {
475
- fieldRefNodes.forEach((fieldRefNode) => {
476
- let name = fieldRefNode.getAttribute("Name") || fieldRefNode.getAttribute("name");
477
- if (!isNullOrEmptyString(name)) {
478
- columns.push(name);
479
- }
480
- });
481
- }
482
- }
483
-
484
- return columns;
485
- }
486
-
487
- function _ensureOrderByColumns(orderByStatement: { Name: string; IsAscending: boolean; }[], columns: string[]) {
488
- //Issue 548: Ensure that the order by field is in the view fields and columns collection so
489
- //that paging works correctly when there is an order by clause
490
- if (orderByStatement.length > 0 && !isNullOrEmptyString(orderByStatement[0].Name)) {
491
- // just add them to columns, and we will add them to view fields below.
492
- // camlQuery = EnsureViewFields(camlQuery, orderByStatement.orderBy.map(o => o.Name), false);
493
- orderByStatement.forEach(o => {
494
- if (columns.indexOf(o.Name) === -1) {
495
- columns.push(o.Name);
496
- }
497
- });
498
- }
499
-
500
- return columns;
501
- }
502
-
503
- function _normalizeColumns(columns: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
504
- return makeUniqueArray(columns.map((column) => {
505
- //some columns will come in lower case (from dvp), normalize them so that we can remove duplicates
506
- let name = Object.keys(allListFieldsHash).filter((fieldName) => {
507
- return fieldName.toLowerCase() === column.toLowerCase();
508
- })[0];
509
- if (!isNullOrEmptyString(name)) {
510
- let field = allListFieldsHash[name];
511
- return field;
512
- }
513
- return null;
514
- }).filter((field) => {
515
- return !isNullOrUndefined(field);
516
- }).map((field) => {
517
- return field.InternalName;
518
- }).concat(['Title', 'Id', 'FileLeafRef', 'FileDirRef', 'FileRef', 'FileSystemObjectType']));
519
- }
520
-
521
- function _removeSingleAndConditions(viewNode: Element, queryNode: Element) {
522
- if (!isNullOrUndefined(viewNode.querySelector("And, and"))) {
523
- try {
524
- //Issue 8063: calendar list, will wrongly add a wrapping <and> statement for a single condition
525
- //will result in error 500
526
- //ie: <View Scope='RecursiveAll'><Query><Where><And><Eq><FieldRef Name=\"Title\" /><Value Type=\"Text\">Holiday</Value></Eq></And></Where>
527
- //</Query><RowLimit>5000</RowLimit></View>
528
- let whereNode = queryNode && queryNode.querySelector("Where, where");
529
-
530
- if (!isNullOrUndefined(whereNode)) {
531
- let firstCondition = whereNode.firstElementChild;
532
- if (firstCondition.tagName.toLowerCase() === "and" && firstCondition.children.length < 2) {
533
- //this is the bug, <and> tag must have 2 conditions - get rid of it!
534
- whereNode.innerHTML = firstCondition.innerHTML;
535
- return {
536
- camlQuery: viewNode.outerHTML
537
- };
538
- }
539
- }
540
- } catch (e) { }
541
- }
542
-
543
- return null;
544
- }
545
-
546
- function _parseRowLimitNode(rowLimitNode: Element, viewNode: Element, rowLimit: number) {
547
- if (!isNullOrUndefined(rowLimitNode)) {
548
- let value = rowLimitNode.textContent;
549
- //if not provided by options - use it
550
- if (isNullOrNaN(rowLimit) && isNumeric(value) && Number(value) > 0) {
551
- rowLimit = Number(value);
552
- }
553
- //remove it
554
- viewNode.removeChild(rowLimitNode);
555
- return {
556
- camlQuery: viewNode.outerHTML,
557
- rowLimit: rowLimit
558
- };
559
- }
560
- return null;
561
- }
562
-
563
- function _processColumns(columns: string[], expandFields: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
564
- let thresholdLimitLookupCount = 0;
565
- let thresholdLimitLookupHit = false;
566
- let selectFields: string[] = [];
567
- let viewFields: string[] = [];
568
- let needContentTypes = false;
569
-
570
- //column parsing
571
- columns.forEach(viewField => {
572
- let viewFieldLower = viewField.toLowerCase();
573
- if (viewFieldLower === 'contenttype' || viewFieldLower === 'contenttypeid') {
574
- needContentTypes = true;
575
- } else if (viewFieldLower === '_moderationstatus') {
576
- selectFields.push(`FieldValuesAsText/${viewField}`);
577
- viewFields.push('_moderationstatus');
578
- } else if (viewFieldLower === '_moderationcomments') {
579
- selectFields.push('OData_' + viewField);
580
- viewFields.push('_moderationcomments');
581
- } else if (viewFieldLower === "filesystemobjecttype") {
582
- selectFields.push("FileSystemObjectType");
583
- } else if (viewFieldLower === "fileref" || viewFieldLower === "filedirref") {
584
- //treat them similar to lookup fields
585
- selectFields.push(viewField);
586
- selectFields.push(`FieldValuesAsText/${viewField}`);
587
- viewFields.push(`${viewField}`);
588
- viewFields.push(`${viewField}Id`);
589
- } else {
590
- //prefer to get columns not from FieldValuesAsText, unless special data type requires it (date, lookup, boolean, etc...)
591
- //make the select url shorter
592
- let foundField = allListFieldsHash[viewField];
593
- if (foundField) {
594
- let foundFieldInternalName = foundField.InternalName;
595
- viewFields.push(foundFieldInternalName);
596
-
597
- let urlField: IFieldInfoEX = null;
598
-
599
- if (foundField.TypeAsString === "URL" && foundField.InternalName === "URL") {
600
- urlField = foundField;
601
- } else if ((viewFieldLower === "urlnomenu"
602
- || viewFieldLower === "urlwmenu2"
603
- || viewFieldLower === "urlwmenu")
604
- && foundField.TypeAsString === "Computed"
605
- && !isNullOrUndefined(allListFieldsHash["URL"])
606
- && allListFieldsHash["URL"].TypeAsString === "URL") {
607
- urlField = allListFieldsHash["URL"];
608
- }
609
-
610
- if (!isNullOrUndefined(urlField) && urlField.TypeAsString === "URL") {
611
- viewFields.push(urlField.InternalName);
612
- selectFields.push(urlField.InternalName);
613
- selectFields.push(`FieldValuesAsText/${urlField.InternalName}`);
614
- } else {
615
- //Issue 828, 336
616
- if (foundFieldInternalName.startsWith("_")) {
617
- foundFieldInternalName = `OData_${foundFieldInternalName}`;
618
- }
619
-
620
- let outputType = getFieldOutputType(foundField);
621
-
622
- switch (outputType) {
623
- case "Lookup":
624
- case "LookupMulti":
625
- case "User":
626
- case "UserMulti":
627
- thresholdLimitLookupCount += 1;
628
- if (thresholdLimitLookupCount >= lookupOrUserFieldLimit) {
629
- thresholdLimitLookupHit = true;
630
- }
631
- //lookup raw values comes with Id appended
632
- selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
633
- selectFields.push(`${foundFieldInternalName}Id`);
634
- break;
635
- case "Boolean":
636
- case "Attachments":
637
- case "AllDayEvent":
638
- case "Recurrence":
639
- selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
640
- selectFields.push(foundFieldInternalName);
641
- break;
642
- default:
643
- selectFields.push(foundFieldInternalName);
644
- break;
645
- }
646
- }
647
- }
648
- else {
649
- selectFields.push(viewField);
650
- }
651
- }
652
- });
653
-
654
- if (needContentTypes) {
655
- expandFields.push("ContentType");
656
- selectFields.push("ContentType/Name");
657
- selectFields.push("ContentTypeId");
658
- viewFields.push("ContentTypeId");
659
- }
660
-
661
- return {
662
- thresholdLimitLookupHit,
663
- viewFields: makeUniqueArray(viewFields),
664
- selectFields: makeUniqueArray(selectFields),
665
- expandFields: makeUniqueArray(expandFields),
666
- needContentTypes
667
- };
668
- }
669
-
670
- function _isLookupOrUserField(field: IFieldInfoEX) {
671
- if (isNullOrUndefined(field)) {
672
- return false;
673
- }
674
- let outputType = getFieldOutputType(field);
675
-
676
- switch (outputType) {
677
- case "Lookup":
678
- case "LookupMulti":
679
- case "User":
680
- case "UserMulti":
681
- return true;
682
- default:
683
- return false;
684
- }
685
- }
686
-
687
- async function processRequestResult(requestUrl: string, restUrlWithSelect: string, camlQuery: string, options: ICamlOptions, siteUrl: string, totalNumberOfItemsToGet: number, batchSize: number, orderByStatement): Promise<{ items: IRestItem[]; postProcessOrderBy: boolean; }> {
688
- //issue 6150: if there are too many fields, url will be too long. just get all columns without adding a $select
689
- if (restUrlWithSelect.length < 2000)
690
- requestUrl = restUrlWithSelect;
691
-
692
- let query: any = { ViewXml: camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`) };
693
-
694
- if (!isNullOrEmptyString(options.FolderServerRelativeUrl))
695
- query.FolderServerRelativeUrl = options.FolderServerRelativeUrl;
696
-
697
- let data = { query: query };
698
-
699
- let items: IRestItem[] = [];
700
- //let triedWithoutViewFields = false;
701
- let postProcessOrderBy = false;
702
- do {
703
- try {
704
- let requestResult = await GetJson<{ value: IRestItem[]; }>(requestUrl, JSON.stringify(data),
705
- {
706
- allowCache: options.refreshCache !== true,
707
- postCacheKey: JSON.stringify(data.query),
708
- jsonMetadata: jsonTypes.nometadata,
709
- spWebUrl: siteUrl
710
- });
711
- if (requestResult && requestResult.value)
712
- items.push(...requestResult.value);
713
-
714
- let itemsLeftToGet = totalNumberOfItemsToGet - items.length;
715
- if (itemsLeftToGet > 0 &&//we need more items
716
- requestResult.value.length === batchSize//we might have more on server since we got the full batch size we asked for
717
- ) {
718
- let lastItem = items[items.length - 1];
719
- let lastItemId = lastItem.Id;
720
- let pagingInfoSort = "";
721
- //Issue 7542 need to add order by value to the paging info if it is in the query
722
- if (!postProcessOrderBy && orderByStatement && orderByStatement.length) {
723
- let orderFieldName = orderByStatement[0].Name;
724
- let orderFieldValue = lastItem[orderFieldName];
725
-
726
- //if field is ID - do not do it.
727
- if (orderFieldName.toLowerCase() !== "id" && !isNullOrUndefined(orderFieldValue)) {
728
- //if value is date - we need it in ISO and in this format: yyyyMMdd HH:mm:ss
729
- try {
730
- //Numbers cast to date properly but they are not dates. ignore number values.
731
- let orderFieldValueAsDate = isNumeric(orderFieldValue) ? null : new Date(orderFieldValue);
732
- if (isDate(orderFieldValueAsDate)) {
733
- try {
734
- //issue 7599 date only field on different time zone...
735
- orderFieldValue = SPServerLocalTimeToUTCSync(siteUrl, orderFieldValueAsDate).replace(/T/i, " ").replace(/Z/i, " ");
736
- } catch (e) { }
737
- }
738
-
739
- } catch (e) { }
740
- pagingInfoSort = `&p_${orderFieldName}=${encodeURIComponentEX(orderFieldValue)}`;
741
- }
742
- }
743
- data.query.ListItemCollectionPosition = {
744
- "PagingInfo": `Paged=TRUE${pagingInfoSort}&p_ID=${lastItemId}`
745
- };
746
- if (itemsLeftToGet < batchSize)//the last batch should be smaller, update the row limit
747
- data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${itemsLeftToGet}</RowLimit></View>`);
748
- }
749
- else
750
- data = null;
751
- } catch (e) {
752
- if (__getSPRestErrorData(e).code.indexOf('SPQueryThrottledException')) {
753
- //test again - on awaiting score view, this will work but will ONLY return the order by fields...
754
- // if (!triedWithoutViewFields) {
755
- // //Our own issues list had too many lookup fields.
756
- // logger.info("Query throttled, trying again without view fields... some fields might be missing from results.");
757
- // triedWithoutViewFields = true;
758
- // camlQuery = EnsureViewFields(camlQuery, orderByStatement.map(o => o.Name), false, true);
759
- // data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
760
- // }
761
- // else
762
- if (!postProcessOrderBy && orderByStatement.length > 0) {
763
- logger.warn("Query throttled, trying again without order by...");
764
- postProcessOrderBy = true;
765
- camlQuery = RemoveOrderByFromCaml(camlQuery);
766
- camlQuery = EnsureViewFields(camlQuery, [], false, true);
767
- data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
768
- }
769
- else throw e;//still throttled? might be due to filter query
770
- }
771
- else throw e;//different error
772
- }
773
- } while (!isNullOrEmptyString(data));
774
- return { items: items, postProcessOrderBy: postProcessOrderBy };
1
+ import { IsLocalDev } from "../../../_dependencies";
2
+ import { chunkArray, firstIndexOf, firstOrNull, makeUniqueArray, toHash } from "../../../helpers/collections.base";
3
+ import { EnsureViewFields, GetOrderByFromCaml, RemoveOrderByFromCaml, getFieldOutputType } from "../../../helpers/sharepoint";
4
+ import { isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumeric } 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 { IFieldInfoEX } from "../../../types/sharepoint.types";
9
+ import { GeListItemsFoldersBehaviour, IRestItem } from "../../../types/sharepoint.utils.types";
10
+ import { ConsoleLogger } from "../../consolelogger";
11
+ import { GetJson } from "../../rest";
12
+ import { GetSiteUrl, __getSPRestErrorData } from "../common";
13
+ import { GetListFields, GetListRestUrl } from "../list";
14
+ import { SPServerLocalTimeToUTCSync } from "../web";
15
+ import { GetItemsById } from "./GetListItemsById";
16
+ import { SkipFields, __fixGetListItemsResults } from "./common";
17
+
18
+ const logger = ConsoleLogger.get("sharepoint.rest/list/GetListItemsByCaml");
19
+
20
+ interface ICamlOptions {
21
+ /** Optional, default: 1000. 0: get all items. */
22
+ rowLimit?: number;
23
+ /** Id, Title, Modified, FileLeafRef, FileDirRef, FileRef, FileSystemObjectType */
24
+ columns: string[];
25
+ foldersBehaviour?: GeListItemsFoldersBehaviour;
26
+ /** set query to get items from this folder only */
27
+ FolderServerRelativeUrl?: string;
28
+ refreshCache?: boolean;
29
+ }
30
+
31
+ //Lookup threshold limit is 12 but we use a smaller number here to ensure there are no issues
32
+ const lookupOrUserFieldLimit = 11;
33
+ /** returns the items or NULL, never errors out since it is used in aggregator.
34
+ * set throwErrors = true if you want errors to be thrown instead of returning null.
35
+ * camlQuery: one of:
36
+ * - view.ViewQuery
37
+ * - wrapped view.ViewQuery in a <View>: "&lt;View Scope='RecursiveAll'>&lt;Query>&lt;/Query>&lt;RowLimit>1000&lt;/RowLimit>&lt;/View>" or send rowLimit in options.
38
+ */
39
+ export async function GetListItemsByCaml(siteUrl: string, listIdOrTitle: string, camlQuery: string, options: ICamlOptions): Promise<IRestItem[]> {
40
+ siteUrl = GetSiteUrl(siteUrl);
41
+
42
+ if (!camlQuery.toLowerCase().startsWith("<view")) {
43
+ camlQuery = `<View Scope='RecursiveAll'><Query>${camlQuery}</Query></View>`;
44
+ }
45
+
46
+ let xmlParser = new DOMParser();
47
+ let xmlDoc = xmlParser.parseFromString(camlQuery, "text/xml");
48
+ let viewNode = xmlDoc.querySelector("View, view");
49
+ let queryNode = viewNode && viewNode.querySelector("Query, query");
50
+ let rowLimitNode = viewNode && viewNode.querySelector("RowLimit, rowlimit");
51
+ let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
52
+
53
+ let parseRowLimitNodeResult = _parseRowLimitNode(rowLimitNode, viewNode, options.rowLimit);
54
+ if (!isNullOrUndefined(parseRowLimitNodeResult)) {
55
+ options.rowLimit = parseRowLimitNodeResult.rowLimit;
56
+ camlQuery = parseRowLimitNodeResult.camlQuery;
57
+ }
58
+
59
+ let removeSingleAndConditionsResult = _removeSingleAndConditions(viewNode, queryNode);
60
+ if (!isNullOrUndefined(removeSingleAndConditionsResult)) {
61
+ camlQuery = removeSingleAndConditionsResult.camlQuery;
62
+ }
63
+
64
+ let getAllItems = isNullOrEmptyString(options.rowLimit) || options.rowLimit < 1;
65
+ let totalItemsInList = 99999;
66
+
67
+ let maxBatchSize = 5000;
68
+ let batchSize = options.rowLimit > 0 && options.rowLimit < maxBatchSize ? options.rowLimit : maxBatchSize;
69
+ let totalNumberOfItemsToGet = getAllItems ? totalItemsInList : options.rowLimit > maxBatchSize ? options.rowLimit : batchSize;
70
+
71
+ let requestUrl = `${GetListRestUrl(siteUrl, listIdOrTitle)}/GetItems?$expand=FieldValuesAsText`;
72
+
73
+ let allListFieldsHash = toHash(await GetListFields(siteUrl, listIdOrTitle), f => f.InternalName);
74
+
75
+ let expandFields: string[] = [];
76
+
77
+ let orderByStatement = GetOrderByFromCaml(camlQuery);
78
+
79
+ if (isNullOrUndefined(options.columns)) {
80
+ options.columns = [];
81
+ }
82
+
83
+ let columns = options.columns;
84
+ columns = _ensureOrderByColumns(orderByStatement, columns);
85
+ columns = _ensureViewFields(viewFieldsNode, columns, allListFieldsHash);
86
+ columns = _normalizeColumns(columns, allListFieldsHash);
87
+
88
+ if (columns.length > options.columns.length) {
89
+ logger.warn(`added ${columns.length - options.columns.length} to query`);
90
+ }
91
+
92
+ options.columns = columns;
93
+
94
+ // Store the result of processing the request
95
+ let rtrnProcessRequestResult: { items: IRestItem[]; postProcessOrderBy: boolean; needContentTypes: boolean };
96
+
97
+ let lookupOrUserFieldsInColumns = options.columns.filter((columnName) => {
98
+ let field = allListFieldsHash[columnName];
99
+ return _isLookupOrUserField(field);
100
+ });
101
+
102
+ let isThrottled = lookupOrUserFieldsInColumns.length >= lookupOrUserFieldLimit;
103
+
104
+ try {
105
+ if (isThrottled) {
106
+ //ISSUE: 1565
107
+ rtrnProcessRequestResult = await _processLookupThresholdCamlRequest(
108
+ orderByStatement,
109
+ allListFieldsHash,
110
+ options,
111
+ camlQuery,
112
+ requestUrl,
113
+ expandFields,
114
+ siteUrl,
115
+ totalNumberOfItemsToGet,
116
+ batchSize);
117
+ } else {
118
+ rtrnProcessRequestResult = await _processNormalCamlRequest(
119
+ orderByStatement,
120
+ allListFieldsHash,
121
+ options,
122
+ camlQuery,
123
+ requestUrl,
124
+ expandFields,
125
+ siteUrl,
126
+ totalNumberOfItemsToGet,
127
+ batchSize);
128
+ }
129
+
130
+ const { items, needContentTypes, postProcessOrderBy } = rtrnProcessRequestResult
131
+
132
+ let itemsResult = __fixGetListItemsResults(siteUrl, listIdOrTitle, items, options.foldersBehaviour);
133
+
134
+ let itemsWithOutContentType: number[] = [];
135
+ if (needContentTypes) {
136
+ itemsResult.forEach((item) => {
137
+ if (isNullOrUndefined(item["ContentType"])) {
138
+ itemsWithOutContentType.push(item.Id);
139
+ } else if (!isNullOrUndefined(item["ContentType"].Name)) {
140
+ item["ContentType"] = item["ContentType"].Name;
141
+ }
142
+ });
143
+ }
144
+
145
+ if (itemsWithOutContentType.length > 0) {
146
+ logger.time("Getting content types");
147
+ //Issue 1465 content types no longer come back from get items request...
148
+ //Make a separate request to get this info
149
+ let ctypes = (await GetItemsById(siteUrl, listIdOrTitle, itemsWithOutContentType, {
150
+ expand: ['ContentType/Name'],
151
+ select: ['ContentType/Name', 'Id'],
152
+ jsonMetadata: jsonTypes.nometadata
153
+ })) as any as { Id: string, ContentType: { Name: string } }[];
154
+
155
+ itemsResult.forEach(item => {
156
+ if (!isNullOrUndefined(ctypes[item.Id])) {
157
+ item["ContentType"] = ctypes[item.Id].ContentType.Name
158
+ }
159
+ });
160
+ logger.timeEnd("Getting content types");
161
+ }
162
+
163
+ if (postProcessOrderBy) {
164
+ //re-apply sort
165
+ if (IsLocalDev) {
166
+ logger.table(itemsResult.map(i => {
167
+ let row = {
168
+ Id: i.Id,
169
+ Title: i.Title
170
+ };
171
+ orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
172
+ return row;
173
+ }), "before sort", true);
174
+ }
175
+ itemsResult.sort((a, b) => {
176
+ for (let i = 0; i < orderByStatement.length; i++) {
177
+ let ob = orderByStatement[i];
178
+ let v1 = a[ob.Name];
179
+ let v2 = b[ob.Name];
180
+ if (v1 === v2) {
181
+ //these are equal - continue to second sort statement
182
+ }
183
+ else {
184
+ if (ob.IsAscending)
185
+ return (v1 > v2) ? 1 : -1;
186
+ else
187
+ return (v2 > v1) ? 1 : -1;
188
+ }
189
+ }
190
+ return 0;
191
+ });
192
+
193
+ if (IsLocalDev) {
194
+ logger.table(itemsResult.map(i => {
195
+ let row = {
196
+ Id: i.Id,
197
+ Title: i.Title
198
+ };
199
+ orderByStatement.forEach(s => { row[`${s.Name} ${s.IsAscending ? 'asc' : 'desc'}`] = i[s.Name]; });
200
+ return row;
201
+ }), "after sort", true);
202
+ }
203
+
204
+ }
205
+
206
+ return itemsResult;
207
+ } catch (ex) {
208
+ console.log(`isThrottled: ${isThrottled}`);
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ // (window as any).runtTest = true;
215
+ // window.setTimeout(() => {
216
+ // if ((window as any).runtTest == false) return;
217
+ // (window as any).runtTest = false;
218
+ // GetListItemsByCaml(null, "aec7756b-daa0-4da1-88ba-c66cb572e816", "", {
219
+ // columns: [],
220
+ // refreshCache: true
221
+ // }).then((r) => {
222
+
223
+ // });
224
+ // }, 500);
225
+
226
+ //ISSUE: 1565
227
+ async function _processLookupThresholdCamlRequest(
228
+ orderByStatement: { Name: string; IsAscending: boolean; }[],
229
+ allListFieldsHash: IDictionary<IFieldInfoEX>,
230
+ options: ICamlOptions,
231
+ camlQuery: string,
232
+ requestUrl: string,
233
+ expandFields: string[],
234
+ siteUrl: string,
235
+ totalNumberOfItemsToGet: number,
236
+ batchSize: number) {
237
+ let rtrnProcessRequestResult = {
238
+ items: [],
239
+ postProcessOrderBy: false,
240
+ needContentTypes: false
241
+ };
242
+
243
+ let orderByFields = orderByStatement.map((orderByField) => {
244
+ let field = allListFieldsHash[orderByField.Name];
245
+ return field;
246
+ });
247
+
248
+ let lookupOrUserFieldsInOrderBy = orderByFields.filter((orderByField) => {
249
+ return _isLookupOrUserField(orderByField);
250
+ });
251
+
252
+ let lookupOrUserFieldsToChunk = options.columns.filter((columnName) => {
253
+ let field = allListFieldsHash[columnName];
254
+ if (_isLookupOrUserField(field)) {
255
+ let col = firstOrNull(lookupOrUserFieldsInOrderBy, (orderByField) => {
256
+ return field.InternalName !== orderByField.InternalName;
257
+ });
258
+
259
+ return col === null;
260
+ }
261
+ return false;
262
+ });
263
+
264
+ let otherFieldNames = options.columns.filter((columnName) => {
265
+ let field = allListFieldsHash[columnName];
266
+ return !isNullOrUndefined(field) && !_isLookupOrUserField(field);
267
+ });
268
+
269
+ //The number of lookup columns in each request is based on the lookupOrUserFieldLimit.
270
+ //Lookup fields in the order by statement must be sent with each request.
271
+ //For example, we have 20 lookup columns and 3 of them are in the order by statement.
272
+ //The 3 order by lookup columns are sent with each request. The remaining lookups are split into chunks.
273
+ //The request will split into 3 requests
274
+ //The first request will have 11 lookup columns (8 standard + 3 order by lookup columns)
275
+ //The second request will have 11 lookup columns (8 standard + 3 order by lookup columns)
276
+ //The third request will have 4 lookup columns (1 standard + 3 order by lookup columns)
277
+ let requestChunks = chunkArray(lookupOrUserFieldsToChunk, lookupOrUserFieldLimit - lookupOrUserFieldsInOrderBy.length);
278
+ let otherFieldsChunkSize = Math.ceil(otherFieldNames.length / requestChunks.length);
279
+ let otherFieldsChunks = chunkArray(otherFieldNames, otherFieldsChunkSize);
280
+
281
+ requestChunks.forEach((chunk, index) => {
282
+ //Add all order by fields to each request, this will include the lookup
283
+ //fields from the order by statement that we left room for previously
284
+ requestChunks[index] = chunk.concat(orderByFields.map((orderByField) => {
285
+ return orderByField.InternalName;
286
+ }));
287
+
288
+ //Add the other fields but split them across the requests so we don't request duplicate data
289
+ if (otherFieldsChunks[index]) {
290
+ requestChunks[index] = chunk.concat(otherFieldsChunks[index]);
291
+ }
292
+ });
293
+
294
+ //requestChunks should now have about the same number of fields in each chunk and each
295
+ //chunk will have all the order by fields.
296
+ let queries = requestChunks.map((requestChunk) => {
297
+ let camlQueryClone = camlQuery;
298
+
299
+ let processColumnsResult = _processColumns(requestChunk, expandFields, allListFieldsHash);
300
+
301
+ //Id field must be included in oder to merge the items correctly
302
+ let viewFields = processColumnsResult.viewFields;
303
+ if (viewFields.length && !viewFields.includes("Id")) {
304
+ viewFields.push("Id");
305
+ }
306
+
307
+ let selectFields = processColumnsResult.selectFields
308
+ if (selectFields.length && !selectFields.includes("Id")) {
309
+ selectFields.push("Id");
310
+ }
311
+
312
+ rtrnProcessRequestResult.needContentTypes = rtrnProcessRequestResult.needContentTypes || processColumnsResult.needContentTypes;
313
+
314
+ expandFields = processColumnsResult.expandFields;
315
+
316
+ if (isNotEmptyArray(viewFields)) {
317
+ camlQueryClone = EnsureViewFields(camlQueryClone, viewFields, true);
318
+ }
319
+
320
+ // if (isDebug()) {
321
+ // let lfields = viewFields.filter((fieldName) => {
322
+ // let field = allListFieldsHash[fieldName];
323
+ // return _isLookupOrUserField(field);
324
+ // });
325
+
326
+ // let xmlDoc = new DOMParser().parseFromString(camlQueryClone, "text/xml");
327
+ // let viewNode = xmlDoc.querySelector("View, view");
328
+ // let viewFieldsNode = viewNode && viewNode.querySelector("ViewFields, viewfields");
329
+
330
+ // let viewFields2 = Array.from(viewFieldsNode.children).map((viewFieldNode) => {
331
+ // let name = viewFieldNode.getAttribute("Name") || viewFieldNode.getAttribute("name");
332
+ // return name;
333
+ // });
334
+
335
+ // let lfields2 = viewFields2.filter((fieldName) => {
336
+ // let field = allListFieldsHash[fieldName];
337
+ // return _isLookupOrUserField(field);
338
+ // });
339
+
340
+ // if (lfields2.length !== lfields.length) {
341
+ // logger.warn("Lookup fields in caml query do not match look up fields in view fields.");
342
+ // logger.warn(`Lookup fields in caml query: ${lfields2}`)
343
+ // logger.warn(`Lookup fields in in view fields: ${lfields}`)
344
+ // }
345
+ // }
346
+
347
+ // Prepare the REST URL with select fields
348
+ let restUrlWithSelect = requestUrl;
349
+ if (expandFields.length > 0) {
350
+ restUrlWithSelect += ',' + expandFields.join(',');
351
+ }
352
+ // Include the lookup user select fields with the other select fields
353
+ restUrlWithSelect += '&$select=' + selectFields.join(',');
354
+ return { camlQueryClone, restUrlWithSelect, viewFields };
355
+ });
356
+
357
+ let mergedItems: IRestItem[] = null;
358
+ for (let i = 0; i < queries.length; i++) {
359
+ const { camlQueryClone, restUrlWithSelect, viewFields } = queries[i];
360
+
361
+ let loopResult = await processRequestResult(requestUrl,
362
+ restUrlWithSelect,
363
+ camlQueryClone,
364
+ { ...options, columns: viewFields },
365
+ siteUrl,
366
+ totalNumberOfItemsToGet,
367
+ batchSize,
368
+ orderByStatement);
369
+
370
+ if (isNullOrUndefined(mergedItems)) {
371
+ mergedItems = loopResult.items;
372
+ } else {
373
+ for (let restItemIndex = 0; restItemIndex < loopResult.items.length; restItemIndex++) {
374
+ //The item chunks (loopResult) should be in the same order as mergedItems because each request should have the
375
+ //same number of items returned in the same order. The only difference between requests is which fields are present.
376
+ //So request 1 will have the 1st set of fields, request 2 will have the 2nd set. etc.
377
+ //This means that the restItemIndex should be the same as existingItemIndex for each iteration. But, we do these extra
378
+ //checks just in case.
379
+ let restItem = loopResult.items[restItemIndex];
380
+ let existingItem = mergedItems[restItemIndex];
381
+ let existingItemIndex = restItemIndex;
382
+ if (isNullOrUndefined(existingItem) || existingItem.Id !== restItem.Id) {
383
+ existingItemIndex = firstIndexOf(mergedItems, (mergedItem) => {
384
+ return mergedItem.Id === restItem.Id;
385
+ });
386
+ }
387
+
388
+ if (existingItemIndex === -1) {
389
+ //We shouldn't get here. Each chunk should have the same items in the same order.
390
+ logger.warn("_processLookupThresholdCamlRequest results are out of sync");
391
+ mergedItems.push(restItem);
392
+ } else {
393
+ let existingItem = mergedItems[existingItemIndex];
394
+ let FieldValuesAsText = {
395
+ ...(existingItem.FieldValuesAsText || {}),
396
+ ...(restItem.FieldValuesAsText || {})
397
+ };
398
+ let FieldValuesForEdit = {
399
+ ...(existingItem.FieldValuesForEdit || {}),
400
+ ...(restItem.FieldValuesForEdit || {})
401
+ };
402
+ existingItem = { ...existingItem, ...restItem };
403
+ existingItem.FieldValuesAsText = FieldValuesAsText;
404
+ existingItem.FieldValuesForEdit = FieldValuesForEdit;
405
+ mergedItems[existingItemIndex] = existingItem;
406
+ }
407
+ }
408
+ }
409
+
410
+ // only need to put this true if it happens once
411
+ if (loopResult.postProcessOrderBy) {
412
+ rtrnProcessRequestResult.postProcessOrderBy = true;
413
+ }
414
+ }
415
+
416
+ rtrnProcessRequestResult.items = mergedItems;
417
+
418
+ return rtrnProcessRequestResult;
419
+ }
420
+
421
+ async function _processNormalCamlRequest(orderByStatement: { Name: string; IsAscending: boolean; }[],
422
+ allListFieldsHash: IDictionary<IFieldInfoEX>,
423
+ options: ICamlOptions,
424
+ camlQuery: string,
425
+ requestUrl: string,
426
+ expandFields: string[],
427
+ siteUrl: string,
428
+ totalNumberOfItemsToGet: number,
429
+ batchSize: number) {
430
+ let processColumnsResult = _processColumns(options.columns, expandFields, allListFieldsHash);
431
+ let viewFields = processColumnsResult.viewFields;
432
+ let selectFields = processColumnsResult.selectFields;
433
+ expandFields = processColumnsResult.expandFields;
434
+ let needContentTypes = processColumnsResult.needContentTypes;
435
+
436
+ if (isNotEmptyArray(viewFields)) {
437
+ camlQuery = EnsureViewFields(camlQuery, viewFields, true);
438
+ }
439
+
440
+ // Prepare the REST URL with select fields
441
+ let restUrlWithSelect = requestUrl;
442
+ if (expandFields.length > 0) {
443
+ restUrlWithSelect += ',' + expandFields.join(',');
444
+ }
445
+ // Include the lookup user select fields with the other select fields
446
+ restUrlWithSelect += '&$select=' + selectFields.join(',');
447
+
448
+ // Process the request and get the result
449
+ let result = await processRequestResult(requestUrl, restUrlWithSelect, camlQuery, options, siteUrl, totalNumberOfItemsToGet, batchSize, orderByStatement);
450
+
451
+ return {
452
+ ...result,
453
+ needContentTypes
454
+ };
455
+ }
456
+
457
+ function _ensureViewFields(viewFieldsNode: Element, columns: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
458
+ //ISSUE: 1565
459
+ //Cases
460
+ //1. Empty view fields element so ALL columns will be requested. We must include all list fields in
461
+ //columns parsing so that we get an accurate count of how many lookup/user fields there are
462
+ //2. View fields element with a some field names that must also be included in the column parsing so we add them here.
463
+ if (isNullOrUndefined(viewFieldsNode) && isNullOrEmptyArray(columns)) {
464
+ Object.keys(allListFieldsHash).forEach((fieldName) => {
465
+ let field = allListFieldsHash[fieldName];
466
+ if (!isNullOrUndefined(field)
467
+ && !field.Hidden
468
+ && !SkipFields.includes(fieldName.toLowerCase())) {
469
+ columns.push(field.InternalName);
470
+ }
471
+ });
472
+ } else if (!isNullOrUndefined(viewFieldsNode)) {
473
+ let fieldRefNodes = Array.from(viewFieldsNode.querySelectorAll("FieldRef"));
474
+ if (isNotEmptyArray(fieldRefNodes)) {
475
+ fieldRefNodes.forEach((fieldRefNode) => {
476
+ let name = fieldRefNode.getAttribute("Name") || fieldRefNode.getAttribute("name");
477
+ if (!isNullOrEmptyString(name)) {
478
+ columns.push(name);
479
+ }
480
+ });
481
+ }
482
+ }
483
+
484
+ return columns;
485
+ }
486
+
487
+ function _ensureOrderByColumns(orderByStatement: { Name: string; IsAscending: boolean; }[], columns: string[]) {
488
+ //Issue 548: Ensure that the order by field is in the view fields and columns collection so
489
+ //that paging works correctly when there is an order by clause
490
+ if (orderByStatement.length > 0 && !isNullOrEmptyString(orderByStatement[0].Name)) {
491
+ // just add them to columns, and we will add them to view fields below.
492
+ // camlQuery = EnsureViewFields(camlQuery, orderByStatement.orderBy.map(o => o.Name), false);
493
+ orderByStatement.forEach(o => {
494
+ if (columns.indexOf(o.Name) === -1) {
495
+ columns.push(o.Name);
496
+ }
497
+ });
498
+ }
499
+
500
+ return columns;
501
+ }
502
+
503
+ function _normalizeColumns(columns: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
504
+ return makeUniqueArray(columns.map((column) => {
505
+ //some columns will come in lower case (from dvp), normalize them so that we can remove duplicates
506
+ let name = Object.keys(allListFieldsHash).filter((fieldName) => {
507
+ return fieldName.toLowerCase() === column.toLowerCase();
508
+ })[0];
509
+ if (!isNullOrEmptyString(name)) {
510
+ let field = allListFieldsHash[name];
511
+ return field;
512
+ }
513
+ return null;
514
+ }).filter((field) => {
515
+ return !isNullOrUndefined(field);
516
+ }).map((field) => {
517
+ return field.InternalName;
518
+ }).concat(['Title', 'Id', 'FileLeafRef', 'FileDirRef', 'FileRef', 'FileSystemObjectType']));
519
+ }
520
+
521
+ function _removeSingleAndConditions(viewNode: Element, queryNode: Element) {
522
+ if (!isNullOrUndefined(viewNode.querySelector("And, and"))) {
523
+ try {
524
+ //Issue 8063: calendar list, will wrongly add a wrapping <and> statement for a single condition
525
+ //will result in error 500
526
+ //ie: <View Scope='RecursiveAll'><Query><Where><And><Eq><FieldRef Name=\"Title\" /><Value Type=\"Text\">Holiday</Value></Eq></And></Where>
527
+ //</Query><RowLimit>5000</RowLimit></View>
528
+ let whereNode = queryNode && queryNode.querySelector("Where, where");
529
+
530
+ if (!isNullOrUndefined(whereNode)) {
531
+ let firstCondition = whereNode.firstElementChild;
532
+ if (firstCondition.tagName.toLowerCase() === "and" && firstCondition.children.length < 2) {
533
+ //this is the bug, <and> tag must have 2 conditions - get rid of it!
534
+ whereNode.innerHTML = firstCondition.innerHTML;
535
+ return {
536
+ camlQuery: viewNode.outerHTML
537
+ };
538
+ }
539
+ }
540
+ } catch (e) { }
541
+ }
542
+
543
+ return null;
544
+ }
545
+
546
+ function _parseRowLimitNode(rowLimitNode: Element, viewNode: Element, rowLimit: number) {
547
+ if (!isNullOrUndefined(rowLimitNode)) {
548
+ let value = rowLimitNode.textContent;
549
+ //if not provided by options - use it
550
+ if (isNullOrNaN(rowLimit) && isNumeric(value) && Number(value) > 0) {
551
+ rowLimit = Number(value);
552
+ }
553
+ //remove it
554
+ viewNode.removeChild(rowLimitNode);
555
+ return {
556
+ camlQuery: viewNode.outerHTML,
557
+ rowLimit: rowLimit
558
+ };
559
+ }
560
+ return null;
561
+ }
562
+
563
+ function _processColumns(columns: string[], expandFields: string[], allListFieldsHash: IDictionary<IFieldInfoEX>) {
564
+ let thresholdLimitLookupCount = 0;
565
+ let thresholdLimitLookupHit = false;
566
+ let selectFields: string[] = [];
567
+ let viewFields: string[] = [];
568
+ let needContentTypes = false;
569
+
570
+ //column parsing
571
+ columns.forEach(viewField => {
572
+ let viewFieldLower = viewField.toLowerCase();
573
+ if (viewFieldLower === 'contenttype' || viewFieldLower === 'contenttypeid') {
574
+ needContentTypes = true;
575
+ } else if (viewFieldLower === '_moderationstatus') {
576
+ selectFields.push(`FieldValuesAsText/${viewField}`);
577
+ viewFields.push('_moderationstatus');
578
+ } else if (viewFieldLower === '_moderationcomments') {
579
+ selectFields.push('OData_' + viewField);
580
+ viewFields.push('_moderationcomments');
581
+ } else if (viewFieldLower === "filesystemobjecttype") {
582
+ selectFields.push("FileSystemObjectType");
583
+ } else if (viewFieldLower === "fileref" || viewFieldLower === "filedirref") {
584
+ //treat them similar to lookup fields
585
+ selectFields.push(viewField);
586
+ selectFields.push(`FieldValuesAsText/${viewField}`);
587
+ viewFields.push(`${viewField}`);
588
+ viewFields.push(`${viewField}Id`);
589
+ } else {
590
+ //prefer to get columns not from FieldValuesAsText, unless special data type requires it (date, lookup, boolean, etc...)
591
+ //make the select url shorter
592
+ let foundField = allListFieldsHash[viewField];
593
+ if (foundField) {
594
+ let foundFieldInternalName = foundField.InternalName;
595
+ viewFields.push(foundFieldInternalName);
596
+
597
+ let urlField: IFieldInfoEX = null;
598
+
599
+ if (foundField.TypeAsString === "URL" && foundField.InternalName === "URL") {
600
+ urlField = foundField;
601
+ } else if ((viewFieldLower === "urlnomenu"
602
+ || viewFieldLower === "urlwmenu2"
603
+ || viewFieldLower === "urlwmenu")
604
+ && foundField.TypeAsString === "Computed"
605
+ && !isNullOrUndefined(allListFieldsHash["URL"])
606
+ && allListFieldsHash["URL"].TypeAsString === "URL") {
607
+ urlField = allListFieldsHash["URL"];
608
+ }
609
+
610
+ if (!isNullOrUndefined(urlField) && urlField.TypeAsString === "URL") {
611
+ viewFields.push(urlField.InternalName);
612
+ selectFields.push(urlField.InternalName);
613
+ selectFields.push(`FieldValuesAsText/${urlField.InternalName}`);
614
+ } else {
615
+ //Issue 828, 336
616
+ if (foundFieldInternalName.startsWith("_")) {
617
+ foundFieldInternalName = `OData_${foundFieldInternalName}`;
618
+ }
619
+
620
+ let outputType = getFieldOutputType(foundField);
621
+
622
+ switch (outputType) {
623
+ case "Lookup":
624
+ case "LookupMulti":
625
+ case "User":
626
+ case "UserMulti":
627
+ thresholdLimitLookupCount += 1;
628
+ if (thresholdLimitLookupCount >= lookupOrUserFieldLimit) {
629
+ thresholdLimitLookupHit = true;
630
+ }
631
+ //lookup raw values comes with Id appended
632
+ selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
633
+ selectFields.push(`${foundFieldInternalName}Id`);
634
+ break;
635
+ case "Boolean":
636
+ case "Attachments":
637
+ case "AllDayEvent":
638
+ case "Recurrence":
639
+ selectFields.push(`FieldValuesAsText/${foundFieldInternalName}`);
640
+ selectFields.push(foundFieldInternalName);
641
+ break;
642
+ default:
643
+ selectFields.push(foundFieldInternalName);
644
+ break;
645
+ }
646
+ }
647
+ }
648
+ else {
649
+ selectFields.push(viewField);
650
+ }
651
+ }
652
+ });
653
+
654
+ if (needContentTypes) {
655
+ expandFields.push("ContentType");
656
+ selectFields.push("ContentType/Name");
657
+ selectFields.push("ContentTypeId");
658
+ viewFields.push("ContentTypeId");
659
+ }
660
+
661
+ return {
662
+ thresholdLimitLookupHit,
663
+ viewFields: makeUniqueArray(viewFields),
664
+ selectFields: makeUniqueArray(selectFields),
665
+ expandFields: makeUniqueArray(expandFields),
666
+ needContentTypes
667
+ };
668
+ }
669
+
670
+ function _isLookupOrUserField(field: IFieldInfoEX) {
671
+ if (isNullOrUndefined(field)) {
672
+ return false;
673
+ }
674
+ let outputType = getFieldOutputType(field);
675
+
676
+ switch (outputType) {
677
+ case "Lookup":
678
+ case "LookupMulti":
679
+ case "User":
680
+ case "UserMulti":
681
+ return true;
682
+ default:
683
+ return false;
684
+ }
685
+ }
686
+
687
+ async function processRequestResult(requestUrl: string, restUrlWithSelect: string, camlQuery: string, options: ICamlOptions, siteUrl: string, totalNumberOfItemsToGet: number, batchSize: number, orderByStatement): Promise<{ items: IRestItem[]; postProcessOrderBy: boolean; }> {
688
+ //issue 6150: if there are too many fields, url will be too long. just get all columns without adding a $select
689
+ if (restUrlWithSelect.length < 2000)
690
+ requestUrl = restUrlWithSelect;
691
+
692
+ let query: any = { ViewXml: camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`) };
693
+
694
+ if (!isNullOrEmptyString(options.FolderServerRelativeUrl))
695
+ query.FolderServerRelativeUrl = options.FolderServerRelativeUrl;
696
+
697
+ let data = { query: query };
698
+
699
+ let items: IRestItem[] = [];
700
+ //let triedWithoutViewFields = false;
701
+ let postProcessOrderBy = false;
702
+ do {
703
+ try {
704
+ let requestResult = await GetJson<{ value: IRestItem[]; }>(requestUrl, JSON.stringify(data),
705
+ {
706
+ allowCache: options.refreshCache !== true,
707
+ postCacheKey: JSON.stringify(data.query),
708
+ jsonMetadata: jsonTypes.nometadata,
709
+ spWebUrl: siteUrl
710
+ });
711
+ if (requestResult && requestResult.value)
712
+ items.push(...requestResult.value);
713
+
714
+ let itemsLeftToGet = totalNumberOfItemsToGet - items.length;
715
+ if (itemsLeftToGet > 0 &&//we need more items
716
+ requestResult.value.length === batchSize//we might have more on server since we got the full batch size we asked for
717
+ ) {
718
+ let lastItem = items[items.length - 1];
719
+ let lastItemId = lastItem.Id;
720
+ let pagingInfoSort = "";
721
+ //Issue 7542 need to add order by value to the paging info if it is in the query
722
+ if (!postProcessOrderBy && orderByStatement && orderByStatement.length) {
723
+ let orderFieldName = orderByStatement[0].Name;
724
+ let orderFieldValue = lastItem[orderFieldName];
725
+
726
+ //if field is ID - do not do it.
727
+ if (orderFieldName.toLowerCase() !== "id" && !isNullOrUndefined(orderFieldValue)) {
728
+ //if value is date - we need it in ISO and in this format: yyyyMMdd HH:mm:ss
729
+ try {
730
+ //Numbers cast to date properly but they are not dates. ignore number values.
731
+ let orderFieldValueAsDate = isNumeric(orderFieldValue) ? null : new Date(orderFieldValue);
732
+ if (isDate(orderFieldValueAsDate)) {
733
+ try {
734
+ //issue 7599 date only field on different time zone...
735
+ orderFieldValue = SPServerLocalTimeToUTCSync(siteUrl, orderFieldValueAsDate).replace(/T/i, " ").replace(/Z/i, " ");
736
+ } catch (e) { }
737
+ }
738
+
739
+ } catch (e) { }
740
+ pagingInfoSort = `&p_${orderFieldName}=${encodeURIComponentEX(orderFieldValue)}`;
741
+ }
742
+ }
743
+ data.query.ListItemCollectionPosition = {
744
+ "PagingInfo": `Paged=TRUE${pagingInfoSort}&p_ID=${lastItemId}`
745
+ };
746
+ if (itemsLeftToGet < batchSize)//the last batch should be smaller, update the row limit
747
+ data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${itemsLeftToGet}</RowLimit></View>`);
748
+ }
749
+ else
750
+ data = null;
751
+ } catch (e) {
752
+ if (__getSPRestErrorData(e).code.indexOf('SPQueryThrottledException')) {
753
+ //test again - on awaiting score view, this will work but will ONLY return the order by fields...
754
+ // if (!triedWithoutViewFields) {
755
+ // //Our own issues list had too many lookup fields.
756
+ // logger.info("Query throttled, trying again without view fields... some fields might be missing from results.");
757
+ // triedWithoutViewFields = true;
758
+ // camlQuery = EnsureViewFields(camlQuery, orderByStatement.map(o => o.Name), false, true);
759
+ // data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
760
+ // }
761
+ // else
762
+ if (!postProcessOrderBy && orderByStatement.length > 0) {
763
+ logger.warn("Query throttled, trying again without order by...");
764
+ postProcessOrderBy = true;
765
+ camlQuery = RemoveOrderByFromCaml(camlQuery);
766
+ camlQuery = EnsureViewFields(camlQuery, [], false, true);
767
+ data.query.ViewXml = camlQuery.replace("</View>", `<RowLimit>${batchSize}</RowLimit></View>`);
768
+ }
769
+ else throw e;//still throttled? might be due to filter query
770
+ }
771
+ else throw e;//different error
772
+ }
773
+ } while (!isNullOrEmptyString(data));
774
+ return { items: items, postProcessOrderBy: postProcessOrderBy };
775
775
  }