@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
458 lines • 22.6 kB
JavaScript
import { chunkArray } from "../../helpers/collections.base";
import { hasOwnProperty } from "../../helpers/objects";
import { promiseNParallel } from "../../helpers/promises";
import { isBoolean, isDate, isNotEmptyArray, 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 { GetFieldNameFromRawValues, GetSiteUrl, __getSPRestErrorData, getFieldNameForUpdate } from "./common";
import { GetList, GetListFields, GetListFieldsAsHash, GetListRestUrl } from "./list";
import { GetUser, GetUserSync } from "./user";
const logger = ConsoleLogger.get("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 });
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 });
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;
}
/** 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
});
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 });
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 });
};
});
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" });
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 })}')`;
try {
let result = await GetJson(url, null, { includeDigestInGet: true, includeDigestInPost: true, xHttpMethod: "DELETE" });
let attachmentFile = result && result.d;
return attachmentFile;
}
catch (e) {
}
return null;
}
//** 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" });
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 });
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) {
//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" });
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 });
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 });
return response.d.EffectiveBasePermissions;
}
//# sourceMappingURL=item.js.map