UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

1,100 lines 55.4 kB
import { PushNoDuplicate, firstOrNull, makeUniqueArray, toHash } from "../../helpers/collections.base"; import { jsonStringify } from "../../helpers/json"; import { jsonClone } from "../../helpers/objects"; import { NormalizeListName, SPBasePermissions, SchemaJsonToXml, SchemaXmlToJson, extendFieldInfos } from "../../helpers/sharepoint"; import { normalizeGuid } from "../../helpers/strings"; import { SafeIfElse, isBoolean, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isPromise, isString, isValidGuid } from "../../helpers/typecheckers"; import { makeServerRelativeUrl, normalizeUrl } from "../../helpers/url"; import { contentTypes, jsonTypes } from "../../types/rest.types"; import { SPBasePermissionKind } from "../../types/sharepoint.types"; import { ConsoleLogger } from "../consolelogger"; import { GetJson, GetJsonSync, longLocalCache, shortLocalCache } from "../rest"; import { GetRestBaseUrl, GetSiteUrl, LIST_EXPAND, LIST_SELECT, __getSPRestErrorData } from "./common"; import { __fixGetListItemsResults } from "./listutils/common"; import { GetContentTypes, GetContentTypesSync, GetListsSync } from "./web"; const logger = ConsoleLogger.get("utils/sharepoint.rest/list"); /** returns /_api/web/lists/getById() or /_api/web/lists/getByTitle() */ export function GetListRestUrl(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); let listId = GetListId(siteUrl, listIdOrTitle); let listPart = isValidGuid(listId) ? `getById('${normalizeGuid(listId)}')` : `getByTitle('${encodeURIComponent(listIdOrTitle)}')`; return GetRestBaseUrl(siteUrl) + `/web/lists/${listPart}`; } export function GetListId(siteUrl, listIdOrTitle) { if (isNullOrEmptyString(listIdOrTitle)) return null; if (isValidGuid(listIdOrTitle)) return listIdOrTitle; //Issue 7508 //When translation is enabled, and user changes list title but he is not on the same language as the site //he translates the list, but not changing its title //so REST api /lists/getByTitle will not work //instead, we need to get the list id from the web's lists collection. let lists = GetListsSync(siteUrl); var lower = listIdOrTitle.toLowerCase(); var list = firstOrNull(lists, l => l.Title.toLowerCase() === lower); return list && list.Id || null; } /** get the list ID from a list page, such as a list view or an item form */ export function GetListIdFromPageSync(siteUrl, listPageUrl) { let url = `${GetRestBaseUrl(siteUrl)}/web/getlist('${makeServerRelativeUrl(listPageUrl.split('?')[0].split('#')[0])}')?$select=id`; let response = GetJsonSync(url, null, { ...longLocalCache, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (!isNullOrUndefined(response) && response.success) { let listId = response.result.Id; return normalizeGuid(listId); } return null; } let siteAssetLibrarySelectFields = [ "ID", "RootFolder/Name", "RootFolder/ServerRelativeUrl", "RootFolder/Exists", "LastItemModifiedDate", "LastItemUserModifiedDate" ]; let siteAssetLibraryExpandFields = [ "RootFolder" ]; /** ensures the site assets library exists and return its info. on errors - it will return null. */ export function EnsureAssetLibrary(siteUrl) { siteUrl = GetSiteUrl(siteUrl); let url = `${GetRestBaseUrl(siteUrl)}/web/lists/EnsureSiteAssetsLibrary?$select=${siteAssetLibrarySelectFields.join(",")}&$expand=${siteAssetLibraryExpandFields.join(",")}`; return GetJson(url, null, { method: "POST", spWebUrl: siteUrl, ...longLocalCache, jsonMetadata: jsonTypes.nometadata, includeDigestInPost: true }).then(response => { if (response) { let result = { Id: response.Id, Name: response.RootFolder.Name, ServerRelativeUrl: response.RootFolder.ServerRelativeUrl, Exists: response.RootFolder.Exists, LastItemModifiedDate: response.LastItemModifiedDate, LastItemUserModifiedDate: response.LastItemUserModifiedDate }; return result; } return null; }).catch(() => null); } /** ensures the site pages library exists and return its info. on errors - it will return null. */ export async function EnsureSitePagesLibrary(siteUrl) { let url = `${GetRestBaseUrl(siteUrl)}/web/lists/EnsureSitePagesLibrary` + `?$select=ID,RootFolder/Name,RootFolder/ServerRelativeUrl,RootFolder/Exists&$expand=RootFolder`; let response = await GetJson(url, null, { method: "POST", jsonMetadata: jsonTypes.nometadata, includeDigestInPost: true, ...longLocalCache, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (!isNullOrUndefined(response) && !isNullOrUndefined(response.RootFolder)) { return { Id: response.Id, Name: response.RootFolder.Name, ServerRelativeUrl: response.RootFolder.ServerRelativeUrl }; } return null; } export function GetSiteAssetLibrary(siteUrl, sync) { siteUrl = GetSiteUrl(siteUrl); // /_api/web/getlist('/sites/qa1/testings/SiteAssets') let reqUrl = `${GetRestBaseUrl(siteUrl)}/web/lists?` //Issue 1492: isSiteAssetsLibrary eq true does not work for reader users. //+ `$filter=isSiteAssetsLibrary eq true&$select=ID,RootFolder/Name,RootFolder/ServerRelativeUrl,RootFolder/Exists` + `$filter=EntityTypeName%20eq%20%27SiteAssets%27` + `&$select=${siteAssetLibrarySelectFields.join(",")}` + `&$expand=${siteAssetLibraryExpandFields.join(",")}`; let caller = sync ? GetJsonSync : GetJson; let response = caller(reqUrl, null, { ...longLocalCache, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); let transform = (values) => { if (!isNullOrEmptyArray(values)) { let v = values[0]; return { Id: v.Id, Name: v.RootFolder.Name, ServerRelativeUrl: v.RootFolder.ServerRelativeUrl, LastItemModifiedDate: v.LastItemModifiedDate, LastItemUserModifiedDate: v.LastItemUserModifiedDate, Exists: v.RootFolder.Exists }; } return null; }; if (isPromise(response)) { return response.then(r => transform(r.value), () => null); } else { return response.success ? transform(response.result.value) : null; } } /** Return the list Title */ export function GetListTitle(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); return GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/Title`, null, { allowCache: true, spWebUrl: siteUrl, jsonMetadata: jsonTypes.nometadata //allow getDigest to work when not in SharePoint }).then(r => { return r.value; }).catch(() => null); } /** Return the list */ export function GetList(siteUrlOrId, listIdOrTitle, options, refreshCache = false) { let siteUrl = GetSiteUrl(siteUrlOrId); if (isNullOrEmptyString(listIdOrTitle)) { return null; } return GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `?$select=${LIST_SELECT}&$expand=${LIST_EXPAND}`, null, { allowCache: true, spWebUrl: siteUrl, //allow getDigest to work when not in SharePoint jsonMetadata: jsonTypes.nometadata }).then(async (list) => { if (options) { let promises = []; if (options.includeViews) { promises.push(GetListViews(siteUrl, listIdOrTitle, options.viewOptions, refreshCache).then((r) => { list.Views = r; })); } if (options.includeContentTypes) { promises.push(GetListContentTypes(siteUrl, listIdOrTitle, null, refreshCache).then((r) => { list.ContentTypes = r; })); } if (options.includeRootFolder) { promises.push(GetListRootFolder(siteUrl, listIdOrTitle).then((r) => { list.RootFolder = r; })); } if (options.includeEventReceivers) { promises.push(GetListEventReceivers(siteUrl, listIdOrTitle, refreshCache).then((r) => { list.EventReceivers = r; })); } if (promises.length > 0) { await Promise.all(promises); } } if (list.EffectiveBasePermissions && (isString(list.EffectiveBasePermissions.High) || isString(list.EffectiveBasePermissions.Low))) { list.EffectiveBasePermissions = { High: Number(list.EffectiveBasePermissions.High), Low: Number(list.EffectiveBasePermissions.Low) }; } return list; }).catch(() => null); } /** Return the list */ export function GetListSync(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); if (isNullOrEmptyString(listIdOrTitle)) return null; let result = GetJsonSync(GetListRestUrl(siteUrl, listIdOrTitle) + `?$select=${LIST_SELECT}&$expand=${LIST_EXPAND}`, null, { ...shortLocalCache, spWebUrl: siteUrl, //allow getDigest to work when not in SharePoint, jsonMetadata: jsonTypes.nometadata }); if (result && result.success) { let list = result.result; if (list.EffectiveBasePermissions && (isString(list.EffectiveBasePermissions.High) || isString(list.EffectiveBasePermissions.Low))) { list.EffectiveBasePermissions = { High: Number(list.EffectiveBasePermissions.High), Low: Number(list.EffectiveBasePermissions.Low) }; } return list; } else return null; } export function GetListNameSync(webUrl, listIdOrTitle) { let list = GetListSync(webUrl, listIdOrTitle); return NormalizeListName({ EntityTypeName: list.EntityTypeName, BaseType: list.BaseType }); } export async function GetListName(webUrl, listIdOrTitle) { let list = await GetList(webUrl, listIdOrTitle); return NormalizeListName({ EntityTypeName: list.EntityTypeName, BaseType: list.BaseType }); } export function GetListRootFolder(siteUrlOrId, listIdOrTitle) { let siteUrl = GetSiteUrl(siteUrlOrId); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/RootFolder?$Select=Name,ServerRelativeUrl`; return GetJson(url, null, { ...longLocalCache, spWebUrl: siteUrl, //allow getDigest to work when not in SharePoint, jsonMetadata: jsonTypes.nometadata }) .then(rootFolder => { return rootFolder; }) .catch(() => null); } export function GetListRootFolderSync(siteUrlOrId, listIdOrTitle) { let siteUrl = GetSiteUrl(siteUrlOrId); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/RootFolder?$Select=Name,ServerRelativeUrl`; let response = GetJsonSync(url, null, { ...longLocalCache, spWebUrl: siteUrl, //allow getDigest to work when not in SharePoint jsonMetadata: jsonTypes.nometadata }); return SafeIfElse(() => response.result, null); } export function GetListField(siteUrlOrId, listIdOrTitle, fieldIdOrName, refreshCache) { let siteUrl = GetSiteUrl(siteUrlOrId); var url = GetListRestUrl(siteUrl, listIdOrTitle) + `/fields`; if (isValidGuid(fieldIdOrName)) { url += `('${normalizeGuid(fieldIdOrName)}')`; } else { url += `/getbyinternalnameortitle(@u)?@u='${encodeURIComponent(fieldIdOrName)}'`; } let result = GetJson(url, null, { allowCache: refreshCache !== true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }) .then(r => { return r.d; }) .catch(() => null); return result; } function _getListFieldsRequestUrl(siteUrl, listIdOrTitle) { return GetListRestUrl(siteUrl, listIdOrTitle) + `/fields`; } /** Gets ID, Title, ContentType Author, Editor, Created and Modified fields */ export function GetStandardListFields(siteUrlOrId, listIdOrTitle, refreshCache) { let fieldNames = ["ID", "Title", "ContentType", "Author", "Editor", "Created", "Modified"]; return GetListFields(siteUrlOrId, listIdOrTitle, { refreshCache: refreshCache, fieldNames: fieldNames }); } function _processGetListFields(fields, fieldNames) { if (isNullOrEmptyArray(fields)) { return fields; } let extendedFields = extendFieldInfos(fields); if (!isNullOrEmptyArray(fieldNames)) { return extendedFields.filter((extendedField) => { return fieldNames.includes(extendedField.InternalName); }); } return extendedFields; } export function GetListFields(siteUrlOrId, listIdOrTitle, options = {}) { let siteUrl = GetSiteUrl(siteUrlOrId); let url = _getListFieldsRequestUrl(siteUrl, listIdOrTitle); let restOptions = { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }; return GetJson(url, null, restOptions) .then((result) => { return _processGetListFields(result.value, options.fieldNames); }).catch(() => { return null; }); } export function GetListFieldsSync(siteUrlOrId, listIdOrTitle, options = {}) { let siteUrl = GetSiteUrl(siteUrlOrId); let url = _getListFieldsRequestUrl(siteUrl, listIdOrTitle); let restOptions = { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }; let result = GetJsonSync(url, null, restOptions); if (result.success && !isNullOrUndefined(result.result)) { return _processGetListFields(result.result.value, options.fieldNames); } return null; } export async function GetListFieldsAsHash(siteUrlOrId, listIdOrTitle, refreshCache) { let siteUrl = GetSiteUrl(siteUrlOrId); let fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: refreshCache }); let hash = {}; if (isNotEmptyArray(fields)) { hash = toHash(fields, f => f.InternalName); } return hash; } export function GetListFieldsAsHashSync(siteUrlOrId, listIdOrTitle, refreshCache) { let siteUrl = GetSiteUrl(siteUrlOrId); let fields = GetListFieldsSync(siteUrl, listIdOrTitle, { refreshCache: refreshCache }); let hash = {}; if (isNotEmptyArray(fields)) { fields.forEach(f => { hash[f.InternalName] = f; }); } return hash; } export function GetListWorkflows(siteUrl, listIdOrTitle, refreshCache) { siteUrl = GetSiteUrl(siteUrl); return GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/workflowAssociations`, null, { allowCache: refreshCache !== true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }) .then(r => { if (r && r.d && isNotEmptyArray(r.d.results)) { r.d.results.forEach(wf => { wf.BaseId = normalizeGuid(wf.BaseId); wf.Id = normalizeGuid(wf.Id); wf.ListId = normalizeGuid(wf.ListId); wf.WebId = normalizeGuid(wf.WebId); }); return r.d.results; } else return []; }) .catch(() => []); } export async function GetListEffectiveBasePermissions(siteUrlOrId, listIdOrTitle) { let siteUrl = GetSiteUrl(siteUrlOrId); let response = await GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/EffectiveBasePermissions`, null, { ...shortLocalCache, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return response.d.EffectiveBasePermissions; } export function GetListEffectiveBasePermissionsSync(siteUrlOrId, listIdOrTitle) { let siteUrl = GetSiteUrl(siteUrlOrId); let response = GetJsonSync(GetListRestUrl(siteUrl, listIdOrTitle) + `/EffectiveBasePermissions`, null, { ...shortLocalCache, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return response.result.d.EffectiveBasePermissions; } export function UserHasManagePermissions(siteUrlOrId, listIdOrTitle) { return GetListEffectiveBasePermissions(siteUrlOrId, listIdOrTitle).then((effectiveBasePermissions) => { return new SPBasePermissions(effectiveBasePermissions).has(SPBasePermissionKind.ManageLists); }).catch(() => null); } export function UserHasEditPermissions(siteUrlOrId, listIdOrTitle) { return UserHasPermissions(siteUrlOrId, listIdOrTitle, SPBasePermissionKind.EditListItems); } export function UserHasPermissions(siteUrlOrId, listIdOrTitle, permissionKind) { return GetListEffectiveBasePermissions(siteUrlOrId, listIdOrTitle).then((effectiveBasePermissions) => { return new SPBasePermissions(effectiveBasePermissions).has(permissionKind); }).catch(() => null); } export function UserHasPermissionsSync(siteUrlOrId, listIdOrTitle, permissionKind) { let effectiveBasePermissions = GetListEffectiveBasePermissionsSync(siteUrlOrId, listIdOrTitle); return new SPBasePermissions(effectiveBasePermissions).has(permissionKind); } /** create a new column and try to add it to default view. Send either Title and Type, or SchemaXml. Create with SchemaXml also adds to all content types */ export async function CreateField(siteUrl, listIdOrTitle, options) { siteUrl = GetSiteUrl(siteUrl); let finish = async (result) => { if (!result) { return null; } let internalName = result.InternalName; //we need to clear and reload the list fields cache, so call it and return our field from that collection. let fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: true }); try { if (options.SkipAddToDefaultView !== true) { if (options.AwaitAddToDefaultView === true) { const views = await GetListViews(siteUrl, listIdOrTitle); let defaultView = firstOrNull(views, v => v.DefaultView); if (defaultView) await GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/views('${defaultView.Id}')/ViewFields/addViewField('${internalName}')`, null, { method: "POST", spWebUrl: siteUrl }); } else { GetListViews(siteUrl, listIdOrTitle).then(views => { let defaultView = firstOrNull(views, v => v.DefaultView); if (defaultView) GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/views('${defaultView.Id}')/ViewFields/addViewField('${internalName}')`, null, { method: "POST", spWebUrl: siteUrl }); }); } } } catch (e) { } let foundField = firstOrNull(fields, f => f.InternalName === internalName); if (!foundField) { //try again... sometimes the new field is not there first try... //especially in inake forms when form is creating 10+ missing columns fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: true }); foundField = firstOrNull(fields, f => f.InternalName === internalName); } return foundField; }; if (!isNullOrEmptyString(options.SchemaXml)) { try { let updateObject = { 'parameters': { '__metadata': { 'type': 'SP.XmlSchemaFieldCreationInformation' }, 'SchemaXml': options.SchemaXml, 'Options': options.SchemaXmlSpecificInternalName !== true ? 4 : //SP.AddFieldOptions.addToAllContentTypes 4 | 8 //SP.AddFieldOptions.addToAllContentTypes | addFieldInternalNameHint } }; let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/fields/createFieldAsXml`; let newFieldResult = await GetJson(url, JSON.stringify(updateObject), { spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (!isNullOrUndefined(newFieldResult) && !isNullOrUndefined(newFieldResult.d)) { if ((!isNullOrEmptyString(options.Title) && options.Title !== newFieldResult.d.Title) || (isBoolean(options.Indexed) && options.Indexed !== newFieldResult.d.Indexed)) { let updatedField = await UpdateField(siteUrl, listIdOrTitle, newFieldResult.d.InternalName, { Title: options.Title, Indexed: options.Indexed === true }); return finish(updatedField); } } return finish(newFieldResult && newFieldResult.d); } catch { } return null; } else if (!isNullOrEmptyString(options.Title) && !isNullOrUndefined(options.Type)) { let updateObject = { '__metadata': { 'type': 'SP.Field' }, 'Title': options.Title, 'FieldTypeKind': options.Type, 'Required': options.Required === true, 'Indexed': options.Indexed === true }; if (!isNullOrEmptyString(options.ClientSideComponentId)) { updateObject.ClientSideComponentId = options.ClientSideComponentId; } if (!isNullOrEmptyString(options.ClientSideComponentProperties)) { updateObject.ClientSideComponentProperties = options.ClientSideComponentProperties; } if (!isNullOrEmptyString(options.JSLink)) { updateObject.JSLink = options.JSLink; } try { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/fields`; let newFieldResult = await GetJson(url, JSON.stringify(updateObject), { spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return finish(newFieldResult && newFieldResult.d); } catch { } return null; } else { console.error("You must send either SchemaXml or Title and Type"); return null; } } /** Update field SchemaXml OR Title, only 1 update at a time supported. */ export async function UpdateField(siteUrlOrId, listIdOrTitle, fieldInternalName, options) { let siteUrl = GetSiteUrl(siteUrlOrId); let finish = async () => { //we need to clear and reload the list fields cache, so call it and return our field from that collection. let fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: true }); return firstOrNull(fields, f => f.InternalName === fieldInternalName); }; let fields = await GetListFieldsAsHash(siteUrl, listIdOrTitle, true); let thisField = fields[fieldInternalName]; //updates can either be SchemaXml, or others. Cannot be both. let updates = { '__metadata': { 'type': 'SP.Field' } }; if (!isNullOrEmptyString(options.SchemaXml)) { updates.SchemaXml = options.SchemaXml; } else { //cannot send schema updates with other updates. if (!isNullOrEmptyString(options.Title)) { updates.Title = options.Title; } if (!isNullOrEmptyString(options.FieldType)) { updates.TypeAsString = options.FieldType; } if (isBoolean(options.Required)) { updates.Required = options.Required === true; } if (isBoolean(options.Indexed)) { updates.Indexed = options.Indexed === true; } if (!isNullOrEmptyArray(options.Choices)) { let choiceType = options.FieldType || thisField.TypeAsString; if (choiceType === "Choice" || choiceType === "MultiChoice") { updates["__metadata"]["type"] = choiceType === "Choice" ? "SP.FieldChoice" : "SP.FieldMultiChoice"; updates.Choices = { "results": options.Choices }; } else { logger.warn("Can only update 'Choices' property on 'Choice' and 'MultiChoice' field types."); } } if (isBoolean(options.Hidden)) { //this requries the CanToggleHidden to be in the schema... if not - we will need to add it before we can update this. let fields = await GetListFieldsAsHash(siteUrl, listIdOrTitle, false); let thisField = fields[fieldInternalName]; if (thisField.Hidden !== options.Hidden) { if (thisField) { if (thisField.SchemaJson.Attributes.CanToggleHidden !== "TRUE") { await UpdateField(siteUrl, listIdOrTitle, fieldInternalName, { SchemaXml: thisField.SchemaXml.replace("<Field ", `<Field CanToggleHidden="TRUE" `) }); } } updates.Hidden = options.Hidden === true; } } if (!isNullOrEmptyString(options.ClientSideComponentId)) updates.ClientSideComponentId = options.ClientSideComponentId; if (!isNullOrEmptyString(options.ClientSideComponentProperties)) updates.ClientSideComponentProperties = options.ClientSideComponentProperties; if (!isNullOrEmptyString(options.JSLink)) updates.JSLink = options.JSLink; } if (Object.keys(updates).length > 1) { return GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/fields/getbyinternalnameortitle('${fieldInternalName}')`, JSON.stringify(updates), { xHttpMethod: "MERGE", spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }) .then(r => { return finish(); }) .catch(() => null); } else { console.error("You must send an option to update"); return null; } } export async function ChangeTextFieldMode(siteUrlOrId, listIdOrTitle, textMode, currentField) { const newSchema = jsonClone(currentField.SchemaJson); const currentSchemaAttributes = newSchema.Attributes; switch (textMode) { case "singleline": let shouldIntermediateUpdate = false; if (currentSchemaAttributes.RichText === 'TRUE') { currentSchemaAttributes.RichText = 'FALSE'; shouldIntermediateUpdate = true; } ; if (currentSchemaAttributes.RichTextMode === 'FullHTML') { currentSchemaAttributes.RichTextMode = 'Compatible'; shouldIntermediateUpdate = true; } ; if (shouldIntermediateUpdate) { const intermediateSchema = SchemaJsonToXml(newSchema); const intermediateUpdatedField = await UpdateField(siteUrlOrId, listIdOrTitle, currentField.InternalName, { SchemaXml: intermediateSchema }); // Early exit if intermediate change failed. if (isNullOrUndefined(intermediateUpdatedField)) return false; } ; // Actual type update. currentSchemaAttributes.Type = 'Text'; delete currentSchemaAttributes.RichTextMode; delete currentSchemaAttributes.RichText; break; case "multiline": currentSchemaAttributes.Type = 'Note'; currentSchemaAttributes.RichText = 'FALSE'; currentSchemaAttributes.RichTextMode = 'Compatible'; break; case "html": currentSchemaAttributes.Type = 'Note'; currentSchemaAttributes.RichText = 'TRUE'; currentSchemaAttributes.RichTextMode = 'FullHTML'; break; } const updatedSchema = SchemaJsonToXml(newSchema); const fieldUpdated = await UpdateField(siteUrlOrId, listIdOrTitle, currentField.InternalName, { SchemaXml: updatedSchema }); // If object is null or undefined then request has failed. return !isNullOrUndefined(fieldUpdated); } export async function ChangeDatetimeFieldMode(siteUrlOrId, listIdOrTitle, includeTime, currentField) { const dateTimeFormat = 'DateTime'; const dateOnlyFormat = 'DateOnly'; const newSchema = jsonClone(currentField.SchemaJson); const fieldAttributes = newSchema.Attributes; let needUpdate = false; if (includeTime && fieldAttributes.Format === dateOnlyFormat) { needUpdate = true; fieldAttributes.Format = dateTimeFormat; } else if (!includeTime && fieldAttributes.Format === dateTimeFormat) { needUpdate = true; fieldAttributes.Format = dateOnlyFormat; } if (needUpdate) { const updatedSchema = SchemaJsonToXml(newSchema); const updateResponse = await UpdateField(siteUrlOrId, listIdOrTitle, currentField.InternalName, { SchemaXml: updatedSchema }); return !isNullOrUndefined(updateResponse); } // If an already existing format was chosen. return true; } export async function DeleteField(siteUrl, listIdOrTitle, fieldInternalName, options) { siteUrl = GetSiteUrl(siteUrl); // let finish = async () => { // //we need to clear and reload the list fields cache, so call it and return our field from that collection. // let fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: true }); // return firstOrNull(fields, f => f.InternalName === fieldInternalName); // }; if (options && options.DeleteHiddenField) await UpdateField(siteUrl, listIdOrTitle, fieldInternalName, { Hidden: false }); return GetJson(GetListRestUrl(siteUrl, listIdOrTitle) + `/fields/getbyinternalnameortitle('${fieldInternalName}')`, null, { method: "POST", xHttpMethod: "DELETE", spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }) .then(r => true) .catch((e) => false); } export function GetListViews(siteUrl, listIdOrTitle, options, refreshCache = false) { siteUrl = GetSiteUrl(siteUrl); let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/views?$select=Title,Id,ServerRelativeUrl,RowLimit,Paged,ViewQuery,ListViewXml,PersonalView,MobileView,MobileDefaultView,Hidden,DefaultView,ReadOnlyView,CustomFormatter${options && options.includeViewFields ? "&$expand=ViewFields" : ""}`; return GetJson(url, null, { allowCache: refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }) .then(r => { let views = r.value; if (isNotEmptyArray(views)) { views.forEach(v => { v.Id = normalizeGuid(v.Id); if (options && options.includeViewFields) { v.ViewFields = v.ViewFields && v.ViewFields["Items"] && v.ViewFields["Items"] || []; } }); } return views; }) .catch(() => null); } export function GetListViewsSync(siteUrl, listIdOrTitle, options, refreshCache = false) { siteUrl = GetSiteUrl(siteUrl); let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/views?$select=Title,Id,ServerRelativeUrl,RowLimit,Paged,ViewQuery,ListViewXml,PersonalView,MobileView,MobileDefaultView,Hidden,DefaultView,ReadOnlyView${options && options.includeViewFields ? "&$expand=ViewFields" : ""}`; let response = GetJsonSync(url, null, { allowCache: refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (response.success) { let views = response && response.result && response.result.value; if (isNotEmptyArray(views)) { views.forEach(v => { v.Id = normalizeGuid(v.Id); }); } return views; } return null; } export async function AddViewFieldToListView(siteUrl, listIdOrTitle, viewId, viewField, refreshCache = false) { return _addOrRemoveViewField(siteUrl, listIdOrTitle, viewId, viewField, "addviewfield", refreshCache); } export async function RemoveViewFieldFromListView(siteUrl, listIdOrTitle, viewId, viewField, refreshCache = false) { return _addOrRemoveViewField(siteUrl, listIdOrTitle, viewId, viewField, "removeviewfield", refreshCache); } async function _addOrRemoveViewField(siteUrl, listIdOrTitle, viewId, viewField, action, refreshCache) { siteUrl = GetSiteUrl(siteUrl); if (isNullOrEmptyString(viewField) || !isValidGuid(viewId)) { return false; } let views = await GetListViews(siteUrl, listIdOrTitle, { includeViewFields: true }, refreshCache); if (isNullOrEmptyArray(views)) { return false; } let view = views.filter((view) => { return normalizeGuid(view.Id) === normalizeGuid(viewId); })[0]; if (isNullOrUndefined(view)) { return false; } let hasField = view.ViewFields.includes(viewField); if (action === "addviewfield" && hasField === true) { return true; } if (action === "removeviewfield" && hasField === false) { return true; } try { let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/views('${normalizeGuid(view.Id)}')/viewfields/${action}('${viewField}')`; let result = await GetJson(url, null, { method: "POST", allowCache: false, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (result && (result.d.AddViewField === null || result.d.RemoveViewField === null)) { return true; } } catch { } return false; } export function GetListContentTypes(siteUrl, listIdOrTitle, options, refreshCache = false) { return GetContentTypes(siteUrl, { ...(options || {}), listIdOrTitle: listIdOrTitle }, refreshCache); } export function GetListContentTypesSync(siteUrl, listIdOrTitle, options, refreshCache = false) { return GetContentTypesSync(siteUrl, { ...(options || {}), listIdOrTitle: listIdOrTitle }, refreshCache); } /** generic version. for the KWIZ forms version that supports action id call GetListFormUrlAppsWeb instead */ export function GetListFormUrl(siteUrl, listId, pageType, params) { siteUrl = GetSiteUrl(siteUrl); if (!isValidGuid(listId)) console.error('GetListFormUrl requires a list id'); let url = `${normalizeUrl(siteUrl)}/_layouts/15/listform.aspx?PageType=${pageType}&ListId=${encodeURIComponent(listId)}`; if (params) { if (!isNullOrEmptyString(params.contentTypeId)) url += `&ContentTypeId=${encodeURIComponent(params.contentTypeId)}`; if (!isNullOrEmptyString(params.itemId)) url += `&ID=${encodeURIComponent(params.itemId)}`; if (!isNullOrEmptyString(params.rootFolder)) url += `&RootFolder=${encodeURIComponent(params.rootFolder)}`; } return url; } export function GetFieldSchemaSync(siteUrl, listIdOrTitle, fieldInternalName, refreshCache) { siteUrl = GetSiteUrl(siteUrl); //ISSUE: 1516 - The get schema request will fail if the field doesn't exist in the list, so we load the fields and ensure the field //exists before requesting the schema. let fields = GetListFieldsSync(siteUrl, listIdOrTitle, { refreshCache: refreshCache, fieldNames: [fieldInternalName] }); if (isNullOrEmptyArray(fields)) { return null; } let field = fields[0]; return SchemaXmlToJson(field.SchemaXml); // let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/fields/getByInternalNameOrTitle('${fieldInternalName}')?$select=SchemaXml`; // let result = GetJsonSync<{ d: { SchemaXml: string; }; }>( // url, // null, // { // ...shortLocalCache, // forceCacheUpdate: refreshCache === true // }); // if (result && result.success) { // return SchemaXmlToJson(result.result.d.SchemaXml); // } // return null; //#endregion } export async function GetFieldSchema(siteUrl, listIdOrTitle, fieldInternalName, refreshCache) { siteUrl = GetSiteUrl(siteUrl); //ISSUE: 1516 - The get schema request will fail if the field doesn't exist in the list, so we load the fields and ensure the field //exists before requesting the schema let fields = await GetListFields(siteUrl, listIdOrTitle, { refreshCache: refreshCache, fieldNames: [fieldInternalName] }); if (isNullOrEmptyArray(fields)) { return null; } let field = fields[0]; return SchemaXmlToJson(field.SchemaXml); } export async function GetListItems(siteUrl, listIdOrTitle, options) { let info = _GetListItemsInfo(siteUrl, listIdOrTitle, options); let items = []; do { let resultItems = []; let next = null; if (info.noMetadata) { let requestResult = (await GetJson(info.requestUrl, null, { allowCache: options.refreshCache !== true, jsonMetadata: options.jsonMetadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint })); resultItems = requestResult.value; next = requestResult["odata.nextLink"]; } else { let requestResult = (await GetJson(info.requestUrl, null, { allowCache: options.refreshCache !== true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint })); resultItems = requestResult.d.results; next = requestResult.d.__next; } if (isNotEmptyArray(resultItems)) items.push(...resultItems); if (info.totalNumberOfItemsToGet > items.length) info.requestUrl = next; else info.requestUrl = null; } while (!isNullOrEmptyString(info.requestUrl)); return __fixGetListItemsResults(siteUrl, listIdOrTitle, items, options.foldersBehaviour, info.expandedLookupFields); } export function GetListItemsSync(siteUrl, listIdOrTitle, options) { let info = _GetListItemsInfo(siteUrl, listIdOrTitle, options); let items = []; do { let resultItems = []; let next = null; if (info.noMetadata) { let requestResult = GetJsonSync(info.requestUrl, null, { allowCache: true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (requestResult.success) { resultItems = requestResult.result.value; next = requestResult.result["odata.nextLink"]; } } else { let requestResult = GetJsonSync(info.requestUrl, null, { allowCache: true, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (requestResult.success) { resultItems = requestResult.result.d.results; next = requestResult.result.d.__next; } } if (isNotEmptyArray(resultItems)) items.push(...resultItems); if (info.totalNumberOfItemsToGet > items.length) info.requestUrl = next; else info.requestUrl = null; } while (!isNullOrEmptyString(info.requestUrl)); return __fixGetListItemsResults(siteUrl, listIdOrTitle, items, options.foldersBehaviour, info.expandedLookupFields); } function _GetListItemsInfo(siteUrl, listIdOrTitle, options) { siteUrl = GetSiteUrl(siteUrl); let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items`; let queryParams = []; //Issue 8189 expand lookup fields let columns = []; let expand = []; let expandedLookupFields = []; if (isNotEmptyArray(options.columns)) options.columns.forEach(c => { if (isString(c)) columns.push(c); else { let internalName = c.InternalName; //Issue 828, 336 if (internalName.startsWith("_")) internalName = `OData_${internalName}`; let isLookupField = c.TypeAsString === "Lookup" || c.TypeAsString === "LookupMulti"; let isUserField = c.TypeAsString === "User" || c.TypeAsString === "UserMulti"; if (isLookupField || isUserField) { //ISSUE: 1519 - Added lookupField property to able to retrieve value of the additional lookup field key let lookupField = c.LookupField; if (!isNullOrEmptyString(lookupField) && isLookupField) { columns.push(`${internalName}/${lookupField}`); } //we want to expand it columns.push(`${internalName}/Title`); columns.push(`${internalName}/Id`); expand.push(internalName); expandedLookupFields.push(c); } else columns.push(internalName); } }); if (isNotEmptyArray(options.expand)) { expand.push(...options.expand); } //add the ones we need PushNoDuplicate(columns, "Id"); PushNoDuplicate(columns, "FileRef"); PushNoDuplicate(columns, "FileSystemObjectType"); if (!isNullOrUndefined(options.columns)) //if got null run without a $select, useful when we need many columns and query string is too long queryParams.push(`$select=${encodeURIComponent(makeUniqueArray(columns).join(','))}`); if (isNotEmptyArray(expand)) queryParams.push(`$expand=${encodeURIComponent(makeUniqueArray(expand).join(','))}`); let batchSize = 2000; let limit = options.rowLimit >= 0 && options.rowLimit < batchSize ? options.rowLimit : batchSize; let totalNumberOfItemsToGet = !isNumber(options.rowLimit) || options.rowLimit < 1 ? 99999 : options.rowLimit > batchSize ? options.rowLimit : limit; if (!isNullOrEmptyString(options.$filter)) queryParams.push(`$filter=${options.$filter}`); queryParams.push(`$top=${limit}`); let requestUrl = url + (queryParams.length > 0 ? '?' + queryParams.join('&') : ''); let noMetadata = options.jsonMetadata === jsonTypes.nometadata; return { requestUrl, noMetadata, totalNumberOfItemsToGet, expandedLookupFields }; } /** Find an item by id, even if it is nested in a sub-folder */ export function FindListItemById(items, itemId) { for (let i = 0; i < items.length; i++) { let current = items[i]; if (current.Id === itemId) return current; else if (isNotEmptyArray(current.__Items)) //folder? look inside { let nestedResult = FindListItemById(current.__Items, itemId); if (!isNullOrUndefined(nestedResult)) return nestedResult; } } //not found return null; } function _getListEventReceiversRequestUrl(siteUrl, listIdOrTitle) { return GetListRestUrl(siteUrl, listIdOrTitle) + `/EventReceivers`; } export async function GetListEventReceivers(siteUrl, listIdOrTitle, refreshCache) { try { let url = _getListEventReceiversRequestUrl(siteUrl, listIdOrTitle); let response = await GetJson(url, null, { allowCache: refreshCache !== true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return !isNullOrUndefined(response) ? response.value : null; } catch { } return null; } export async function AddListEventReceiver(siteUrl, listIdOrTitle, eventReceiverDefinition) { let newEventReceiver = { ReceiverAssembly: "", ReceiverClass: "", ...eventReceiverDefinition }; try { let url = _getListEventReceiversRequestUrl(siteUrl, listIdOrTitle); let response = await GetJson(url, JSON.stringify(newEventReceiver), { method: "POST", includeDigestInPost: true, jsonMetadata: jsonTypes.nometadata, headers: { "content-type": contentTypes.json }, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return !isNullOrUndefined(response) && isValidGuid(response.ReceiverId) ? response : null; } catch { } return null; } export async function DeleteListEventReceiver(siteUrl, listIdOrTitle, eventReceiverId) { try { let url = `${_getListEventReceiversRequestUrl(siteUrl, listIdOrTitle)}('${normalizeGuid(eventReceiverId)}')/deleteObject`; let response = await GetJson(url, null, { method: "POST", includeDigestInPost: true, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); return !isNullOrUndefined(response) && response["odata.null"] === true; } catch { } return false; } export function GetListLastItemModifiedDate(siteUrl, listIdOrTitle, options) { siteUrl = GetSiteUrl(siteUrl); let endPoint = options && options.userChangesOnly ? 'LastItemUserModifiedDate' : 'LastItemModifiedDate'; let sync = options && options.sync ? true : false; let caller = sync ? GetJsonSync : GetJson; let result = caller(GetListRestUrl(siteUrl, listIdOrTitle) + `/${endPoint}`, null, { allowCache: true, //in memory only jsonMetadata: jsonTypes.nometadata, forceCacheUpdate: options && options.refreshCache === true || false, spWebUrl: siteUrl //allow getDigest to work when not in SharePoint }); if (isPromise(result)) return result.then(r => r.value, () => null); else return result.success ? result.result.value : null; } export async function ReloadListLastModified(siteUrl, listIdOrTitle) { await GetListLastItemModifiedDate(siteUrl, listIdOrTitle, { refreshCache: true }); //make sure we do it for both title and id, we don't know how the other callers may use this in their API if (!isValidGuid(listIdOrTitle)) { try { var listId = GetListId(siteUrl, listIdOrTitle); await GetListLastItemModifiedDate(siteUrl, listId, { refreshCache: true }); } catch (e) { } } else { try { var listTitle = await GetListTitle(siteUrl, listIdOrTitle); await GetListLastItemModifiedDate(siteUrl, listTitle, { refreshCache: true }); } catch (e) { } } } export async function ListHasUniquePermissions(siteUrl, listIdOrTitle) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/?$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 RestoreListPermissionInheritance(siteUrl, listIdOrTitle) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/ResetRoleInheritance`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function BreakListPermissionInheritance(siteUrl, listIdOrTitle, clear = true) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/breakroleinheritance(copyRoleAssignments=${clear ? 'false' : 'true'}, clearSubscopes=true)`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function AssignListPermission(siteUrl, listIdOrTitle, principalId, roleId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/roleassignments/addroleassignment(principalid=${principalId},roleDefId=${roleId})`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function RemoveListPermission(siteUrl, listIdOrTitle, principalId, roleId) { let url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/roleassignments/removeroleassignment(principalid=${principalId},roleDefId=${roleId})`; await GetJson(url, undefined, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } export async function CreateList(siteUrl, info) { let url = `${GetRestBaseUrl(siteUrl)}/web/lists`; const body = { __metadata: { type: 'SP.List' }, AllowContentTypes: false, ContentTypesEnabled: false, BaseTemplate: info.template, BaseType: info.type, Description: info.description, Title: info.title }; let newList = (await GetJson(url, jsonStringify(body), { spWebUrl: siteUrl //allow getDigest to work when not in SharePoint })).d; normalizeGuid(newList.Id); return newList; } export async function RecycleList(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); const url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/recycle()`; const result = { recycled: true }; try { await GetJson(url, null, { method: "POST", allowCache: false, jsonMetadata: jsonTypes.nometadata, spWebUrl: siteUrl }); } catch (e) { result.recycled = false; result.errorMessage = __getSPRestErrorData(e).message; } return result; } export async function DeleteList(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); const url = `${GetListRestUrl(siteUrl, listIdOrTitle)}/deleteObject`; const result = { deleted: true }; try