UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

530 lines 25.6 kB
import { chunkArray } from "../../helpers/collections.base"; import { hasOwnProperty } from "../../helpers/objects"; import { promiseNParallel } from "../../helpers/promises"; import { isBoolean, isDate, isNotEmptyArray, isNotEmptyString, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isObject, isString } from "../../helpers/typecheckers"; import { encodeURIComponentEX } from "../../helpers/url"; import { jsonTypes } from "../../types/rest.types"; import { DateTimeFieldFormatType } from "../../types/sharepoint.types"; import { LocaleKnownScript } from "../../utils/knownscript"; import { ConsoleLogger } from "../consolelogger"; import { GetJson, GetJsonSync, shortLocalCache } from "../rest"; import { DecodeFieldValuesAsTextKey, GetFieldNameFromRawValues, GetSiteUrl, __getSPRestErrorData, getFieldNameForUpdate, hasGlobalContext } from "./common"; import { GetList, GetListFields, GetListFieldsAsHash, GetListRestUrl } from "./list"; import { SPVersionToVersionId } from "./listutils/common"; import { GetUser, GetUserSync } from "./user"; const logger = ConsoleLogger.get("utils/sharepoint.rest/item"); /** can only select FileSizeDisplay in REST api */ export const FileSizeColumnInternalNames = ["FileSizeDisplay", "File_x0020_Size"]; function _getListItemSelectExpandFields(fields, listFields) { var $selectFields = []; var $expandFields = []; fields.forEach((fieldName) => { if (FileSizeColumnInternalNames.includes(fieldName)) { $selectFields.push(FileSizeColumnInternalNames[0]); //for some reason, can't select File_x0020_Size } else { let field = listFields.filter((listField) => { return listField.InternalName === fieldName; })[0]; if (!isNullOrUndefined(field)) { if (field.TypeAsString === "User" || field.TypeAsString === "UserMulti") { $selectFields.push(`${field.InternalName}/ID`); $selectFields.push(`${field.InternalName}/Name`); $selectFields.push(`${field.InternalName}/UserName`); $selectFields.push(`${field.InternalName}/EMail`); $selectFields.push(`${field.InternalName}/Title`); $expandFields.push(field.InternalName); } else { $selectFields.push(field.InternalName); } } } }); return { expandFields: $expandFields, selectFields: $selectFields }; } function _parseValueFromRawValue(rawValue, asDisplayValue = false) { if (!isNullOrUndefined(rawValue)) { if (rawValue["ID"] && rawValue["Title"] && rawValue["Name"]) { //expanded user field from rest request return !asDisplayValue ? rawValue["ID"] : rawValue["Title"]; } else if (Array.isArray(rawValue)) { return rawValue.map((value) => { if (value["ID"] && value["Title"] && value["Name"]) { //expanded user field from rest request return !asDisplayValue ? value["ID"] : value["Title"]; } return value; }).filter((value) => { return value !== null; }); } else { return rawValue; } } } async function _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options) { siteUrl = GetSiteUrl(siteUrl); options = options || {}; let listFields = await GetListFields(siteUrl, listIdOrTitle); var { selectFields, expandFields } = _getListItemSelectExpandFields(fields, listFields); var $select = `$select=` + encodeURIComponent(selectFields.length ? `${selectFields.join(',')}` : fields.join(',')); var $expand = expandFields.length ? `$expand=${encodeURIComponent(expandFields.join(','))}` : ""; let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})?${$select}&${$expand}`; let result = await GetJson(url, null, { allowCache: options.refreshCache !== true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); var values = {}; if (result && typeof (result.d) !== "undefined") { var rawValues = result.d; fields.forEach((fieldName) => { let rawValue = (FileSizeColumnInternalNames.includes(fieldName)) ? rawValues[FileSizeColumnInternalNames[0]] : rawValues[fieldName]; if (!isNullOrUndefined(rawValue)) { values[fieldName] = rawValue; } }); } return values; } export function GetListItemFieldDisplayValueSync(siteUrl, listIdOrTitle, itemId, field) { return GetListItemFieldDisplayValuesSync(siteUrl, listIdOrTitle, itemId, [field])[field]; } export function GetListItemFieldDisplayValuesSync(siteUrl, listIdOrTitle, itemId, fields) { let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/FieldValuesAsText?$select=${fields.join(',')}`; let result = GetJsonSync(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return result.success ? result.result : {}; } export async function GetListItemFieldDisplayValue(siteUrl, listIdOrTitle, itemId, field, options) { var values = await GetListItemFieldDisplayValues(siteUrl, listIdOrTitle, itemId, [field], options); return values && values[field]; } export async function GetListItemFieldDisplayValues(siteUrl, listIdOrTitle, itemId, fields, options) { var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options); var values = {}; Object.keys(rawValues).forEach(key => { var fieldValue = _parseValueFromRawValue(rawValues[key], true); if (!isNullOrUndefined(fieldValue)) { values[key] = fieldValue; } }); return values; } export async function GetListItemFieldValue(siteUrl, listIdOrTitle, itemId, field, options) { var values = await GetListItemFieldValues(siteUrl, listIdOrTitle, itemId, [field], options); return values && values[field]; } export async function GetListItemFieldValues(siteUrl, listIdOrTitle, itemId, fields, options) { var rawValues = await _getListItemRawFieldValues(siteUrl, listIdOrTitle, itemId, fields, options); var values = {}; Object.keys(rawValues).forEach(key => { var fieldValue = _parseValueFromRawValue(rawValues[key]); if (!isNullOrUndefined(fieldValue)) { values[key] = fieldValue; } }); return values; } /** version #.# or version ID as number */ export async function GetListItem(siteUrl, listIdOrTitle, itemId, version, options) { if (!options) options = {}; siteUrl = GetSiteUrl(siteUrl); let versionPart = ""; if (isNumber(version) && version > 0 || isNotEmptyString(version)) { versionPart = `/versions(${SPVersionToVersionId(version)})`; } let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})${versionPart}`; let result = await GetJson(url, null, { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (isNotEmptyString(versionPart)) //versions come back as values as text, need to clean them up for consistency { Object.keys(result).forEach(internalNameAsText => { let internalName = DecodeFieldValuesAsTextKey(internalNameAsText); let value = result[internalNameAsText]; if (!isNullOrUndefined(value)) { if (isString(value.StringValue)) value = value.StringValue; //ContentTypeId else if (isNumber(value.LookupId)) { internalName = `${internalName}Id`; value = value.LookupId; } else if (Array.isArray(value)) { if (isNumber(value[0].LookupId)) { internalName = `${internalName}Id`; value = value.map(v => v.LookupId); } } } result[internalName] = value; }); } return result; } /** Returns version array, newest version first. Can get moderator comments, cannot get file check in comments */ export async function GetListItemFieldValuesHistory(siteUrl, listIdOrTitle, itemId, fields, options) { siteUrl = GetSiteUrl(siteUrl); options = options || {}; var $select = isNotEmptyArray(fields) ? `$select=` + encodeURIComponent(`${fields.join(',')}`) : ""; let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/versions?${$select}`; let result = await GetJson(url, null, { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return result && result.value || []; } export async function DeleteListItem(siteUrl, listIdOrTitle, itemId) { siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})`; let result = { deleted: true }; try { await GetJson(url, null, { method: "POST", spWebUrl: siteUrl, xHttpMethod: "DELETE" }); //empty string means deleted } catch (e) { result.deleted = false; result.errorMessage = __getSPRestErrorData(e).message; } return result; } export async function RecycleListItem(siteUrl, listIdOrTitle, itemId) { siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/recycle()`; let result = { recycled: true }; try { await GetJson(url, null, { method: "POST", spWebUrl: siteUrl }); //value.d.Recycle will hold guide reference id } catch (e) { result.recycled = false; result.errorMessage = __getSPRestErrorData(e).message; } return result; } export async function GetListItemAttachments(siteUrl, listIdOrTitle, itemId) { siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles`; try { let result = await GetJson(url, null, { includeDigestInGet: true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); let attachmentFiles = result.d && result.d.results ? result.d.results : []; return attachmentFiles; } catch (e) { } return []; } export async function GetListItemsAttachments(siteUrl, listIdOrTitle, itemIds) { siteUrl = GetSiteUrl(siteUrl); let chunks = chunkArray(itemIds, 30); let select = `$select=ID,AttachmentFiles`; let expand = `$expand=AttachmentFiles`; let baseUrl = GetListRestUrl(siteUrl, listIdOrTitle) + `/items`; let promises = chunks.map((chunk) => { return () => { let filter = `$filter=${chunk.map(i => `ID eq ${i}`).join(" or ")}`; let url = `${baseUrl}?${select}&${filter}&${expand}`; return GetJson(url, null, { includeDigestInGet: true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); }; }); try { let result = await promiseNParallel(promises, 5); return result && result.length > 0 ? result.map(v => v.value) : []; } catch { } return []; } export async function AddAttachment(siteUrl, listIdOrTitle, itemId, filename, buffer) { siteUrl = GetSiteUrl(siteUrl); //Issue 999 let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles/add(FileName='${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`; try { let result = await GetJson(url, buffer, { includeDigestInPost: true, method: "POST", spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); let attachmentFile = result && result.d; return attachmentFile; } catch (e) { } return null; } export async function DeleteAttachment(siteUrl, listIdOrTitle, itemId, filename) { siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/AttachmentFiles('${encodeURIComponentEX(filename, { singleQuoteMultiplier: 2 })}')`; let result = { deleted: true }; try { await GetJson(url, null, { spWebUrl: siteUrl, method: "POST", xHttpMethod: "DELETE" }); } catch (e) { result.deleted = false; result.errorMessage = __getSPRestErrorData(e).message; } return result; } //** Update value of taxonomy multi-value field. See issue 7585 for more info */ export async function UpdateMultiTaxonomyValue(siteUrl, listIdOrTitle, itemId, updates) { let fields = updates && Object.keys(updates) || []; if (isNullOrEmptyArray(fields)) return []; siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`; try { let result = await GetJson(url, JSON.stringify({ bNewDocumentUpdate: false, checkInComment: null, formValues: fields.map(field => ({ ErrorMessage: null, FieldName: field, FieldValue: updates[field].map(v => `${v.Label}|${v.TermGuid};`).join(''), HasException: false })) }), { includeDigestInPost: true, method: "POST", spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || []; } catch (e) { logger.error(`Error updating UpdateMultiTaxonomyValue ${e}`); } return fields.map(f => ({ field: f, error: 'Unspecified update error' })); } export async function AddItem(siteUrl, listIdOrTitle, fieldValues) { //we must force creating even if no values, otherwise the item won't be created at all. return UpdateItem(siteUrl, listIdOrTitle, null, fieldValues, { updateIfNoFields: true }); } export async function UpdateItem(siteUrl, listIdOrTitle, itemId, fieldValues, options) { var success = false; var error = null; try { siteUrl = GetSiteUrl(siteUrl); let isNewItem = itemId > 0 ? false : true; let listInfo = await GetList(siteUrl, listIdOrTitle); let fields = await GetListFieldsAsHash(siteUrl, listIdOrTitle); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items${isNewItem ? '' : `(${itemId})`}`; var itemUpdateInfo = { '__metadata': { 'type': `SP.Data.${listInfo.EntityTypeName}Item` } }; let hasUpdates = false; Object.keys(fieldValues).forEach(updateField => { let listField = fields[updateField]; if (listField) //make sure this field exists on the list { //todo: we might want to get the value first, make sure it is formatted correctly for the field type. itemUpdateInfo[getFieldNameForUpdate(listField)] = fieldValues[updateField]; hasUpdates = true; } }); if (!hasUpdates) { let forceUpdate = options && options.updateIfNoFields; if (!forceUpdate) return { success: true, itemId: itemId }; } var xHttpMethod = isNewItem ? null : "MERGE"; try { let result = await GetJson(url, JSON.stringify(itemUpdateInfo), { method: "POST", xHttpMethod: xHttpMethod, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (result) if (isNewItem) itemId = result.d.Id; // update item will not return data. only new item will. success = true; } catch (e) { error = __getSPRestErrorData(e).message; } } catch (e) { logger.group(() => logger.error(e), 'Update item failed'); } return { success: success, errorMessage: error, itemId: itemId }; } /** Get a sharepoint field value as text, from a rest item */ export function GetSPRestItemValueAsText(item, field) { var otherName = field.InternalName.replace(/_/g, "_x005f_"); //sometimes _ gets replaced with _x005f_ if (!isNullOrUndefined(item.FieldValuesAsText)) { if (hasOwnProperty(item.FieldValuesAsText, field.InternalName)) return item.FieldValuesAsText[field.InternalName]; if (hasOwnProperty(item.FieldValuesAsText, otherName)) return item.FieldValuesAsText[otherName]; } return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field).join(', '); } /** Get a sharepoint field value as text array, from a rest item */ export function GetSPRestItemValueAsTextArray(item, field) { //get value as text first let valueAsText = null; var otherName = field.InternalName.replace(/_/g, "_x005f_"); //sometimes _ gets replaced with _x005f_ if (!isNullOrUndefined(item.FieldValuesAsText)) { if (hasOwnProperty(item.FieldValuesAsText, field.InternalName)) valueAsText = item.FieldValuesAsText[field.InternalName]; if (hasOwnProperty(item.FieldValuesAsText, otherName)) valueAsText = item.FieldValuesAsText[otherName]; } if (!isNullOrEmptyString(valueAsText) && valueAsText.indexOf(',') < 0) //not empty, and we do not suspect a multi-value field return [valueAsText]; return GetSPFieldValueAsText(item[GetFieldNameFromRawValues(field)], field); } /** prefer to use GetSPRestValueAsText instead */ export function GetSPFieldValueAsText(value, field) { let locales = LocaleKnownScript.loadSync(); let culture = locales.GetCurrentCulture(); let rawValues = isNullOrEmptyString(value) ? [] : Array.isArray(value) ? value //value.raw is an array : [value]; //value.raw is not an array - wrap it. let isLookup = field.TypeAsString === "Lookup" || field.TypeAsString === "LookupMulti"; let isUser = field.TypeAsString === "User" || field.TypeAsString === "UserMulti"; let isCounter = field.TypeAsString === "Counter" || field.TypeAsString === "Integer"; let isTaxonomy = field.TypeAsString === "TaxonomyFieldType" || field.TypeAsString === "TaxonomyFieldTypeMulti"; if (field.TypeAsString === "DateTime") { //Issue 8190 - date field might come as string rawValues = rawValues.map(v => isDate(v) ? v : new Date(v)); } else if (isUser || isLookup) { rawValues = rawValues.map(v => isNumber(v) ? v : !isNullOrEmptyString(v && v.Title) ? v.Title : isNumber(v && v.Id) ? v.Id : null); } let textResults = []; if (isNotEmptyArray(rawValues)) { rawValues.forEach(raw => { if (isNullOrEmptyString(raw)) { /** skip */ } else if (isNumber(raw)) if (isUser && hasGlobalContext()) { //todo - try not sync... try { let userInfo = GetUserSync(_spPageContextInfo.siteServerRelativeUrl, raw); textResults.push(userInfo.Title); } catch (e) { textResults.push(`${raw}`); } } else if (isLookup) { //todo - not supported try { textResults.push(`Lookup #${raw}`); } catch (e) { textResults.push(`${raw}`); } } else if (isCounter) { textResults.push(raw.toString()); } else { textResults.push(locales.NumberToString(raw, culture, { isCurrency: isNumber(field.CurrencyLocaleId), isPercent: field.ShowAsPercentage })); } else if (isTaxonomy) { if (isNotEmptyArray(raw)) { textResults.push(raw.map(t => `${t.Label}|${t.TermGuid}`).join(';')); } else if (isObject(raw) && raw !== null && 'Label' in raw) { textResults.push(raw.Label || ''); } } else if (isString(raw)) textResults.push(raw); else if (isBoolean(raw)) { textResults.push(raw ? "Yes" : "No"); } else if (isDate(raw)) { textResults.push(locales.DateToString(raw, culture, { includeDate: true, includeTime: field.DisplayFormat === DateTimeFieldFormatType.DateTime })); } }); } return textResults; } /** set an existing item system info: author, editor, created and modified dates */ export async function SetItemCreatedModifiedInfo(siteUrl, listIdOrTitle, itemId, updates) { let updateValues = {}; let fields = updates && Object.keys(updates) || []; if (!isNullOrEmptyString(updates.Created)) //date must be yyyy-MM-dd hh:mm:ss updateValues.Created = (isString(updates.Created) ? new Date(updates.Created) : updates.Created).toISOString().replace('T', ' ').split('.')[0]; if (!isNullOrEmptyString(updates.Modified)) updateValues.Modified = (isString(updates.Modified) ? new Date(updates.Modified) : updates.Modified).toISOString().replace('T', ' ').split('.')[0]; if (updates.AuthorId > 0) { let asUser = await GetUser(siteUrl, updates.AuthorId); updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`; //[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}] } if (updates.EditorId > 0) { let asUser = await GetUser(siteUrl, updates.EditorId); updateValues.AuthorId = `[{'Key':'${asUser.UserPrincipalName}'}]`; //[{'Key':'i:0#.f|membership|user@Tenant.onmicrosoft.com'}] } if (isNullOrEmptyArray(fields)) return []; siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/ValidateUpdateListItem()`; try { let result = await GetJson(url, JSON.stringify({ formValues: fields.map(field => ({ FieldName: field, FieldValue: updateValues[field] })) }), { method: "POST", spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return result && result.d && result.d.ValidateUpdateListItem.results.map(v => ({ field: v.FieldName, error: v.ErrorMEssage })) || []; } catch (e) { logger.error(`Error updating values ${e}`); } return fields.map(f => ({ field: f, error: 'Unspecified update error' })); } export async function ListItemHasUniquePermissions(siteUrl, listIdOrTitle, itemId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/?$select=hasuniqueroleassignments`; let has = await GetJson(url, undefined, { allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return has.HasUniqueRoleAssignments === true; } export async function RestoreListItemPermissionInheritance(siteUrl, listIdOrTitle, itemId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/ResetRoleInheritance`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function BreakListItemPermissionInheritance(siteUrl, listIdOrTitle, itemId, clear = true) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/breakroleinheritance(copyRoleAssignments=${clear ? 'false' : 'true'}, clearSubscopes=true)`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function AssignListItemPermission(siteUrl, listIdOrTitle, itemId, principalId, roleId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/addroleassignment(principalid=${principalId},roleDefId=${roleId})`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function RemoveListItemPermission(siteUrl, listIdOrTitle, itemId, principalId, roleId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/items(${itemId})/roleassignments/removeroleassignment(principalid=${principalId},roleDefId=${roleId})`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function GetItemEffectiveBasePermissions(siteUrlOrId, listIdOrTitle, itemId) { let siteUrl = GetSiteUrl(siteUrlOrId); let response = await GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/items(${itemId})/EffectiveBasePermissions`, null, { ...shortLocalCache, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return response.d.EffectiveBasePermissions; } //# sourceMappingURL=item.js.map