@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
1,201 lines (1,032 loc) • 58 kB
text/typescript
import { sortArray } from "../../helpers/collections.base";
import { jsonStringify } from "../../helpers/json";
import { getGlobal } from "../../helpers/objects";
import { SPBasePermissions, extendFieldInfo } from "../../helpers/sharepoint";
import { normalizeGuid } from "../../helpers/strings";
import { isDate, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrNaN, isNullOrUndefined, isNumeric, isString, isTypeofFullNameNullOrUndefined, isValidGuid } from "../../helpers/typecheckers";
import { makeFullUrl, makeServerRelativeUrl, normalizeUrl } from "../../helpers/url";
import { IDictionary } from "../../types/common.types";
import { IRestOptions, jsonTypes } from "../../types/rest.types";
import { IContextWebInformation, IFieldInfoEX, IFolderInfo, IRententionLabel, ISiteGroupInfo, IUserCustomActionInfo, IWebInfo, SPBasePermissionKind } from "../../types/sharepoint.types";
import { IAppTile, IGroupInfo, IRestRoleDefinition, IRootWebInfo, ISiteInfo, ITimeZone, IUserInfo, IWebBasicInfo, IWebRegionalSettings, WebTypes, iContentType, iList } from "../../types/sharepoint.utils.types";
import { AutoDiscoverTenantInfo } from "../auth/discovery";
import { ConsoleLogger } from "../consolelogger";
import { toIsoDateFormat } from "../date";
import { GetJson, GetJsonSync, longLocalCache, mediumLocalCache, noLocalCache, shortLocalCache, weeekLongLocalCache } from "../rest";
import { CONTENT_TYPES_SELECT, CONTENT_TYPES_SELECT_WITH_FIELDS, GetRestBaseUrl, GetSiteUrl, LIST_EXPAND, LIST_SELECT, WEB_SELECT, hasGlobalContext } from "./common";
import { GetListFields, GetListFieldsSync, GetListRestUrl } from "./list";
const logger = ConsoleLogger.get("SharePoint.Rest.Web");
export async function GetSiteInfo(siteUrl?: string): Promise<ISiteInfo> {
siteUrl = GetSiteUrl(siteUrl);
try {
const r = await GetJson<{ d: ISiteInfo; }>(GetRestBaseUrl(siteUrl) + "/site?$select=id,serverRelativeUrl", null, { ...longLocalCache });
var id = normalizeGuid(r.d.Id);
var serverRelativeUrl = normalizeUrl(r.d.ServerRelativeUrl);
if (isNullOrEmptyString(serverRelativeUrl)) serverRelativeUrl = "/"; //can't return "" since it will be treated as current sub site, when tyring to access the root site from a sub-site
return { Id: id, ServerRelativeUrl: serverRelativeUrl };
} catch {
return null;
}
}
export function GetSiteInfoSync(siteUrl?: string): ISiteInfo {
siteUrl = GetSiteUrl(siteUrl);
let result = GetJsonSync<{ d: ISiteInfo; }>(GetRestBaseUrl(siteUrl) + "/site?$select=id,serverRelativeUrl", null, { ...longLocalCache });
if (result.success) {
var id = normalizeGuid(result.result.d.Id);
var serverRelativeUrl = normalizeUrl(result.result.d.ServerRelativeUrl);
return { Id: id, ServerRelativeUrl: serverRelativeUrl };
}
return null;
}
function _getSiteIdFromContext(siteUrl?: string) {
if (hasGlobalContext()) {
//issue 7295
//make sure we return false for /sites/ab/c is not under /sites/a by adding a / at the end
let normalizedWebUrl = normalizeUrl(makeServerRelativeUrl(siteUrl), true).toLowerCase();
let normalizedCurrentSiteUrl = normalizeUrl(_spPageContextInfo.siteServerRelativeUrl, true).toLowerCase();
//test cases
//if (!testSub("/", "/hello")) console.error("1");
//if (testSub("/", "/sites/hello")) console.error("2");
//if (testSub("/sites/a", "/sites/b")) console.error("3");
//if (!testSub("/sites/a", "/sites/a/b")) console.error("4");
//if (!testSub("/", "/")) console.error("5");
//if (!testSub("/sites/a", "/sites/a")) console.error("6");
//if (testSub("/sites/a", "/hello")) console.error("7");
if (isNullOrUndefined(siteUrl)
|| normalizedCurrentSiteUrl === "/" && !normalizedWebUrl.startsWith("/sites")
|| normalizedCurrentSiteUrl !== "/" && normalizedWebUrl.startsWith(normalizedCurrentSiteUrl)) {
if (!isNullOrEmptyString(_spPageContextInfo.siteId)) {
return normalizeGuid(_spPageContextInfo.siteId);
}
}
}
return null;
}
/** Get tenant id lower case no {} */
export function GetTenantId() {
if (!isTypeofFullNameNullOrUndefined("_spPageContextInfo")) {
return normalizeGuid(_spPageContextInfo.aadTenantId);
}
let info = AutoDiscoverTenantInfo(true);
if (!isNullOrUndefined(info) && isValidGuid(info.idOrName)) {
return normalizeGuid(info.idOrName);
}
return null;
}
/** Get tenant id lower case no {} */
export function GetPortalUrl() {
return _spPageContextInfo.portalUrl;
}
/** Get site id lower case no {} */
export async function GetSiteId(siteUrl?: string): Promise<string> {
let siteId = _getSiteIdFromContext(siteUrl);
if (!isNullOrEmptyString(siteId)) {
return siteId;
}
return GetSiteInfo(siteUrl).then((info) => {
if (!isNullOrUndefined(info) && !isNullOrEmptyString(info.Id)) {
return normalizeGuid(info.Id);
}
return null;
}).catch<string>(() => {
return null;
});
}
/** Get site id lower case no {} */
export function GetSiteIdSync(siteUrl?: string): string {
let siteId = _getSiteIdFromContext(siteUrl);
if (!isNullOrEmptyString(siteId)) {
return siteId;
}
let result = GetSiteInfoSync(siteUrl);
return !isNullOrUndefined(result) ? normalizeGuid(result.Id) : null;
}
/** Get root web id lower case no {} */
export function GetRootWebInfo(siteUrl?: string): Promise<IRootWebInfo> {
siteUrl = GetSiteUrl(siteUrl);
return GetJson<{ d: IRootWebInfo; }>(GetRestBaseUrl(siteUrl) + "/site/rootWeb?$select=id,serverRelativeUrl", null, { ...longLocalCache })
.then(r => {
var id = normalizeGuid(r.d.Id);
var serverRelativeUrl = normalizeUrl(r.d.ServerRelativeUrl);
//console.log("site id: " + id);
return { Id: id, ServerRelativeUrl: serverRelativeUrl };
})
.catch<IRootWebInfo>(() => null);
}
/** Return the web Title */
export function GetWebTitle(siteUrl: string): Promise<string> {
siteUrl = GetSiteUrl(siteUrl);
return GetJson<{ d: { Title: string; }; }>(GetRestBaseUrl(siteUrl) + `/web/Title`, null, { ...shortLocalCache })
.then(r => {
return r.d.Title;
})
.catch<string>(() => null);
}
function _getWebIdRequestUrl(siteUrl: string) {
return `${GetRestBaseUrl(siteUrl)}/web/Id`;
}
/** Return the web id */
export function GetWebId(siteUrl: string): Promise<string> {
return GetJson<{ d: { Id: string; }; }>(_getWebIdRequestUrl(siteUrl), null, { ...longLocalCache })
.then(r => {
return normalizeGuid(r.d.Id);
})
.catch<string>(() => null);
}
/** Return the web id */
export function GetWebIdSync(siteUrl: string): string {
let syncResult = GetJsonSync<{ d: { Id: string; }; }>(_getWebIdRequestUrl(siteUrl), null, { ...longLocalCache });
if (syncResult.success)
return syncResult.result.d.Id;
else return null;
}
/** Return the web id */
export async function IsRootWeb(siteUrl: string): Promise<boolean> {
siteUrl = GetSiteUrl(siteUrl);
let webId = await GetWebId(siteUrl);
let rootWeb = await GetRootWebInfo(siteUrl);
return webId === rootWeb.Id;
}
export function UserHasAllPermissions(siteUrl: string, permissions: SPBasePermissionKind[]): Promise<boolean> {
siteUrl = GetSiteUrl(siteUrl);
return GetJson<{ d: { EffectiveBasePermissions: { High: number; Low: number; }; }; }>(GetRestBaseUrl(siteUrl) + `/web/EffectiveBasePermissions`, null,
{ ...shortLocalCache })
.then(r => {
var effectivePermissions = new SPBasePermissions(r.d.EffectiveBasePermissions);
return permissions.every((perm) => {
return effectivePermissions.has(perm);
});
})
.catch<boolean>(() => null);
}
export function UserHasManageSitePermissions(siteUrl: string): Promise<boolean> {
siteUrl = GetSiteUrl(siteUrl);
if (!isTypeofFullNameNullOrUndefined("_spPageContextInfo")) {
if (siteUrl.startsWith(_spPageContextInfo.siteServerRelativeUrl))
if (_spPageContextInfo.isSiteAdmin || _spPageContextInfo["isSiteOwner"]) return Promise.resolve(true);
}
return GetJson<{ d: { EffectiveBasePermissions: { High: number; Low: number; }; }; }>(GetRestBaseUrl(siteUrl) + `/web/EffectiveBasePermissions`, null,
{ ...shortLocalCache })
.then(r => {
return new SPBasePermissions(r.d.EffectiveBasePermissions).has(SPBasePermissionKind.ManageWeb);
})
.catch<boolean>(() => null);
}
export interface IGetContentTypesOptions {
/** if you want content types for a specific list under site URL - ignores fromRootWeb */
listIdOrTitle?: string;
/** if you want content types from the root web - ignores listIdOrTitle */
fromRootWeb?: boolean;
ignoreFolders?: boolean;
ignoreHidden?: boolean;
/** Include fields associated with the content type */
includeFields?: boolean;
}
function _getContentTypesRequestUrl(siteUrl: string, options: Omit<IGetContentTypesOptions, "ignoreFolders" | "ignoreHidden"> = {}) {
const { fromRootWeb, includeFields, listIdOrTitle } = options;
let query = `$select=${includeFields === true ? CONTENT_TYPES_SELECT : CONTENT_TYPES_SELECT_WITH_FIELDS}${includeFields === true ? "&$expand=Fields" : ""}`;
if (!isNullOrEmptyString(listIdOrTitle)) {
return `${GetListRestUrl(siteUrl, listIdOrTitle)}/contenttypes?${query}`;
} else if (fromRootWeb) {
return `${GetRestBaseUrl(siteUrl)}/site/rootweb/contenttypes?${query}`;
} else {
return `${GetRestBaseUrl(siteUrl)}/web/contenttypes?${query}`;
}
}
function _postProcessGetContentTypes(contentTypes: iContentType[],
options: Omit<IGetContentTypesOptions, "listIdOrTitle" | "fromRootWeb"> = {},
allListFields?: IFieldInfoEX[]) {
const { ignoreHidden, ignoreFolders, includeFields } = options;
if (!isNullOrEmptyArray(contentTypes)) {
if (ignoreFolders === true || ignoreHidden === true) {
contentTypes = contentTypes.filter(rr => {
if (options.ignoreFolders && rr.StringId.startsWith('0x0120')) return false;
if (options.ignoreHidden && rr.Hidden) return false;
return true;
});
}
if (includeFields === true) {
contentTypes.forEach((result) => {
if (!isNullOrEmptyArray(result.Fields)) {
result.Fields = result.Fields.map((field) => {
return extendFieldInfo(field, allListFields || result.Fields);
});
}
});
}
return contentTypes;
}
return null;
}
export async function GetContentTypes(siteUrl: string, options: IGetContentTypesOptions = {}, refreshCache = false): Promise<iContentType[]> {
let url = _getContentTypesRequestUrl(siteUrl, options);
let allListFields: IFieldInfoEX[] = null;
if (options.includeFields) {
allListFields = await GetListFields(siteUrl, options.listIdOrTitle);
}
return GetJson<{ value: iContentType[]; }>(url, null, { allowCache: refreshCache !== true, jsonMetadata: jsonTypes.nometadata })
.then(result => {
if (!isNullOrUndefined(result)) {
return _postProcessGetContentTypes(result.value, options, allListFields);
}
return null;
})
.catch<iContentType[]>(() => null);
}
export function GetContentTypesSync(siteUrl: string, options: IGetContentTypesOptions = {}, refreshCache = false): iContentType[] {
let url = _getContentTypesRequestUrl(siteUrl, options);
let allListFields: IFieldInfoEX[] = null;
if (options.includeFields) {
allListFields = GetListFieldsSync(siteUrl, options.listIdOrTitle);
}
let result = GetJsonSync<{ value: iContentType[]; }>(url, null, { allowCache: refreshCache !== true, jsonMetadata: jsonTypes.nometadata });
if (!isNullOrUndefined(result) && result.success === true && !isNullOrUndefined(result.result)) {
return _postProcessGetContentTypes(result.result.value, options, allListFields);
}
return null;
}
interface IGetListsOptions {
includeRootFolders?: boolean;
includeViews?: boolean;
}
function _getListsRequestUrl(siteUrl: string, options: IGetListsOptions) {
let select = LIST_SELECT;
let expand = LIST_EXPAND;
if (options.includeRootFolders === true) {
select += ",RootFolder/Name,RootFolder/ServerRelativeUrl";
expand += ",RootFolder";
}
if (options.includeViews === true) {
expand += ",Views";
}
return GetRestBaseUrl(siteUrl) + `/web/lists?$select=${select}&$expand=${expand}`;
}
function _postProcessGetLists(lists: iList[], options: Omit<IGetListsOptions, "includeRootFolders"> = {}) {
lists = lists || [];
if (options && options.includeViews) {
lists.forEach(l => {
if (isNullOrEmptyArray(l.Views)) {
l.Views = [];
}
l.Views.forEach(v => { v.Id = normalizeGuid(v.Id); });
});
}
lists.forEach((list) => {
if (list.EffectiveBasePermissions
&& (isString(list.EffectiveBasePermissions.High)
|| isString(list.EffectiveBasePermissions.Low))) {
list.EffectiveBasePermissions = {
High: Number(list.EffectiveBasePermissions.High),
Low: Number(list.EffectiveBasePermissions.Low)
};
}
});
return lists;
}
export function GetLists(siteUrl: string, options: IGetListsOptions = {}): Promise<iList[]> {
let url = _getListsRequestUrl(siteUrl, options);
return GetJson<{ value: iList[]; }>(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata })
.then(result => {
return _postProcessGetLists(result.value, options);
})
.catch<iList[]>(() => []);
}
export function GetListsSync(siteUrl: string, options: IGetListsOptions = {}): iList[] {
let url = _getListsRequestUrl(siteUrl, options);
let response = GetJsonSync<{ value: iList[]; }>(url, null, { ...shortLocalCache, jsonMetadata: jsonTypes.nometadata });
if (response && response.success && response.result && isNotEmptyArray(response.result.value)) {
return _postProcessGetLists(response.result.value, options);
}
return [];
}
/**
* Get all sub webs. Results will be cached in memory and sorted
* @param siteUrl the starting URL you want to get the sites for
* @param allowAppWebs send true if you would like to inlucde app webs as well
*/
export async function GetAllSubWebs(siteUrl: string, options?: { allSiteCollections?: boolean; allowAppWebs?: boolean; }): Promise<IWebBasicInfo[]> {
siteUrl = GetSiteUrl(siteUrl);
let sites: IWebBasicInfo[] = [];
options = options || {};
var currentSite: IWebBasicInfo;
var queryFailed = false;
try {
currentSite = await GetWebInfo(siteUrl);
let queryFilter = '';
if (!options.allSiteCollections) {
//filter by site id
let siteId = await GetSiteId(siteUrl);
queryFilter = `SiteId:${siteId}`;
}
//Issue 6735 missing WebId for some customer (US, government GCC tenant will not return WebId)
let queryUrl = `${GetRestBaseUrl(siteUrl)}/search/query?querytext=%27${queryFilter}(contentclass:STS_Site)%20(contentclass:STS_Web)%27&trimduplicates=false&rowlimit=5000&selectproperties=%27Title,Url,WebTemplate,WebId%27`;
let response = await GetJson<{
d: {
query: {
PrimaryQueryResult: {
RelevantResults: {
RowCount: number;
TotalRows: number;
TotalRowsIncludingCuplicates: number;
Table: {
Rows: {
results: {
Cells: {
results: {
Key: "Title" | "Url" | "SiteId" | "WebId" | "WebTemplate";
Value: string;
ValueType: "Edm.String" | "Edm.Double" | "Edm.Int64" | "Edm.Int32" | "Edm.Guid" | "Null";
}[];
};
}[];
};
};
};
};
};
};
}>(queryUrl, null, { ...shortLocalCache });
let results = response && response.d && response.d.query && response.d.query.PrimaryQueryResult;
let addedSites: string[] = [];
if (results && results.RelevantResults.RowCount >= 0) {
let allPropsFound = false;
results.RelevantResults.Table.Rows.results.forEach(row => {
let Title: string = null;
let Url: string = null;
let WebId: string = null;
let WebTemplate: string = null;
let skip = false;
for (var i = 0; i < row.Cells.results.length; i++) {
let cell = row.Cells.results[i];
let value = isNullOrEmptyString(cell.Value) ? "" : cell.Value;
switch (cell.Key) {
case "WebTemplate":
WebTemplate = value;
if (!options.allowAppWebs && value === "APP")
skip = true;
break;
case "Title":
Title = value;
break;
case "WebId":
WebId = normalizeGuid(value);
break;
case "Url":
if (addedSites.indexOf(value.toLowerCase()) >= 0) {
//duplicate, skip
skip = true;
}
else {
Url = value;
}
break;
}
if (skip)
break;//stop the cells loop
allPropsFound =
Title !== null &&
Url !== null &&
WebId !== null &&
WebTemplate !== null;
if (allPropsFound)
break;
}
if (!skip && allPropsFound)//don't skip, and we found all needed props
{
sites.push({
Title: Title,
ServerRelativeUrl: makeServerRelativeUrl(Url),
WebId: WebId,
WebTemplate: WebTemplate,
WebType: WebTemplate === "APP" ? WebTypes.App :
WebTemplate === "GROUP" ? WebTypes.Group :
WebTemplate === "STS" ? WebTypes.Team :
WebTypes.Other
});
}
});
}
//Issue 7161
if (sites.length === 1 || (!isNullOrUndefined(currentSite) && !sites.filter((site) => {
return site.WebId !== currentSite.WebId;
})[0])) {
queryFailed = true;
}
} catch (e) {
queryFailed = true;
}
if (queryFailed) {
// Igor: Issue #7702
if (_spPageContextInfo && _spPageContextInfo.siteServerRelativeUrl.toLowerCase() !== siteUrl.toLowerCase()) {
//siteUrl = _spPageContextInfo.siteServerRelativeUrl;
//currentSite = await GetWebInfo(siteUrl);
//Kevin: Issue 1028
//The user may not have permission to the site collection root web. Instead of overwirting the currentSite/siteUrl,
//we make a request for the site collection root web. If we get a valid response, replace currentSite/siteUrl with
//the site collection root web info.
let currentSiteCollection = await GetWebInfo(_spPageContextInfo.siteServerRelativeUrl);
if (currentSiteCollection && !isNullOrEmptyString(currentSiteCollection.ServerRelativeUrl)) {
currentSite = currentSiteCollection;
siteUrl = _spPageContextInfo.siteServerRelativeUrl;
}
}
//add current site
if (currentSite && (options.allowAppWebs || currentSite.WebType !== WebTypes.App)) {
sites.push(currentSite);
}
//Issue 6651
//add sub sites
//if the query failed, we can't rely on search to get the subwebs
var currentSiteSubSites = await __getSubSites(siteUrl, options.allowAppWebs);
if (isNotEmptyArray(currentSiteSubSites)) {
sites = [...sites, ...currentSiteSubSites];
}
}
var webIds = [];
var filteredSites: IWebBasicInfo[] = [];
for (let site of sites) {
if (webIds.indexOf(site.WebId) === -1) {
webIds.push(site.WebId);
filteredSites.push(site);
}
}
sortArray(filteredSites, s => s.ServerRelativeUrl);
return filteredSites;
}
export async function __getSubSites(siteUrl: string, allowAppWebs?: boolean) {
siteUrl = GetSiteUrl(siteUrl);
let sites: IWebBasicInfo[] = [];
//try {
//maybe search is not wokring? use regular REST API
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/getsubwebsfilteredforcurrentuser(nwebtemplatefilter=-1,nconfigurationfilter=0)?$Select=Title,ServerRelativeUrl,Id,WebTemplate`;
let result = await GetJson<{
d: {
results: {
Title: string;
ServerRelativeUrl: string;
Id: string;
WebTemplate: string;
}[];
};
}>(restUrl, null, { ...shortLocalCache });
if (result && result.d && isNotEmptyArray(result.d.results)) {
let results = (allowAppWebs) ? result.d.results : result.d.results.filter(s => s.WebTemplate !== "APP");
let promises: Promise<IWebBasicInfo[]>[] = [];
results.forEach(s => {
sites.push({
Title: s.Title,
ServerRelativeUrl: s.ServerRelativeUrl,
WebId: s.Id,
WebTemplate: s.WebTemplate,
WebType: s.WebTemplate === "APP" ? WebTypes.App :
s.WebTemplate === "GROUP" ? WebTypes.Group :
s.WebTemplate === "STS" ? WebTypes.Team :
WebTypes.Other
});
promises.push(__getSubSites(s.ServerRelativeUrl, allowAppWebs));
});
//loop and add all sub sites
let allSubs = await Promise.all(promises);
allSubs.forEach(subSubs => {
sites.push(...subSubs);
});
}
//}
//catch {
//}
return sites;
}
interface IGetWebInfoResponse {
Title: string;
ServerRelativeUrl: string;
Id: string;
WebTemplate: string;
Description: string,
SiteLogoUrl: string
}
function _getWebInfoByIdRequestUrl(siteUrl: string, webId: string) {
return `${GetRestBaseUrl(siteUrl)}/site/openWebById('${webId}')?$Select=${WEB_SELECT}`;
}
function _getCurrentWebInfoRequestUrl(siteUrl: string) {
return `${GetRestBaseUrl(siteUrl)}/web?$Select=${WEB_SELECT}`;
}
function _postProcessGetWebInfo(webInfo: IGetWebInfoResponse) {
if (!isNullOrUndefined(webInfo)) {
return {
Title: webInfo.Title,
ServerRelativeUrl: webInfo.ServerRelativeUrl,
WebId: webInfo.Id,
WebTemplate: webInfo.WebTemplate,
WebType: GetWebType(webInfo.WebTemplate),
Description: webInfo.Description,
SiteLogoUrl: webInfo.SiteLogoUrl
} as IWebBasicInfo;
}
return null;
}
export async function GetWebInfo(siteUrl: string, webId?: string, refreshCache?: boolean): Promise<IWebBasicInfo> {
let webInfoResponse: IGetWebInfoResponse = null;
try {
if (!isNullOrEmptyString(webId) && isValidGuid(webId)) {
webId = normalizeGuid(webId);
let currentWebId = await GetWebId(siteUrl);
if (currentWebId !== webId) {
let url = _getWebInfoByIdRequestUrl(siteUrl, webId);
webInfoResponse = await GetJson<IGetWebInfoResponse>(url, null, {
method: "POST", spWebUrl: GetSiteUrl(siteUrl), ...shortLocalCache,
jsonMetadata: jsonTypes.nometadata,
allowCache: refreshCache !== true
});
}
}
if (isNullOrUndefined(webInfoResponse)) {
let url = _getCurrentWebInfoRequestUrl(siteUrl);
webInfoResponse = await GetJson<IGetWebInfoResponse>(url, null, {
...shortLocalCache,
jsonMetadata: jsonTypes.nometadata,
allowCache: refreshCache !== true
});
}
} catch (e) { }
return _postProcessGetWebInfo(webInfoResponse);
}
export function GetWebInfoSync(siteUrl: string, webId?: string): IWebBasicInfo {
let webInfoResponse: IGetWebInfoResponse = null;
if (!isNullOrEmptyString(webId) && isValidGuid(webId)) {
webId = normalizeGuid(webId);
let currentWebId = GetWebIdSync(siteUrl);
if (currentWebId !== webId) {
let url = _getWebInfoByIdRequestUrl(siteUrl, webId);
let syncResult = GetJsonSync<IGetWebInfoResponse>(url, null, {
method: "POST", spWebUrl: GetSiteUrl(siteUrl), ...shortLocalCache,
jsonMetadata: jsonTypes.nometadata
});
if (syncResult.success) {
webInfoResponse = syncResult.result;
}
}
}
if (isNullOrUndefined(webInfoResponse)) {
let url = _getCurrentWebInfoRequestUrl(siteUrl);
let syncResult = GetJsonSync<IGetWebInfoResponse>(url, null, {
...shortLocalCache,
jsonMetadata: jsonTypes.nometadata
});
if (syncResult.success) {
webInfoResponse = syncResult.result;
}
}
return _postProcessGetWebInfo(webInfoResponse);
}
export async function GetWebRoleDefinitions(siteUrl: string): Promise<IRestRoleDefinition[]> {
return GetJson<{ d: { results: IRestRoleDefinition[]; }; }>(GetRestBaseUrl(siteUrl) + `/web/RoleDefinitions?filter=Hidden ne true`, null, { ...longLocalCache })
.then(r => {
return r.d.results || [];
})
.catch<IRestRoleDefinition[]>(() => []);
}
export interface iRoleAssignment {
Member: IGroupInfo | IUserInfo,
RoleDefinitionBindings: IRestRoleDefinition[],
PrincipalId: 14
};
/** get roles for site or list */
export async function GetRoleAssignments(siteUrl: string, listIdOrTitle?: string, itemId?: number) {
const url = `${isNullOrEmptyString(listIdOrTitle) ? GetRestBaseUrl(siteUrl) + "/web" : GetListRestUrl(siteUrl, listIdOrTitle)}/${isNullOrNaN(itemId) ? '' : `items(${itemId})/`}roleassignments?$expand=Member/users,RoleDefinitionBindings`;
const result = await GetJson<{ value: iRoleAssignment[] }>(url, undefined, { jsonMetadata: jsonTypes.nometadata });
return result.value;
}
/** Web sub webs for the selected site */
export async function GetSubWebs(siteUrl: string, options?: { allowAppWebs?: boolean; }): Promise<IWebInfo[]> {
return GetJson<{ d: { results: IWebInfo[]; }; }>(GetRestBaseUrl(siteUrl) + `/web/webs${options && options.allowAppWebs ? "" : "&$filter=WebTemplate ne 'APP'"}`, null,
{ ...shortLocalCache })
.then(r => {
return r.d.results;
})
.catch<IWebInfo[]>(() => []);
}
/** Web sub webs for the selected site */
export async function GetAppTiles(siteUrl: string): Promise<IAppTile[]> {
//Issue 933 this api does not work in a classic app web
if (hasGlobalContext() && _spPageContextInfo.isAppWeb) {
logger.warn('GetAppTiles does not work in an app web');
return null;
}
return GetJson<{ value: IAppTile[]; }>(GetRestBaseUrl(siteUrl) + "/web/AppTiles?$filter=AppType%20eq%203&$select=Title,ProductId", null,
{ ...shortLocalCache, jsonMetadata: jsonTypes.nometadata })
.then(r => {
return isNotEmptyArray(r.value) ? r.value.map(t => {
return {
Title: t.Title,
ProductId: normalizeGuid(t.ProductId)
} as IAppTile;
}) : [];
})
.catch<IAppTile[]>(() => []);
}
/** Web sub webs for the selected site */
export function GetAppTilesSync(siteUrl: string): IAppTile[] {
siteUrl = GetSiteUrl(siteUrl);
//Issue 933 this api does not work in a classic app web
if (hasGlobalContext() && _spPageContextInfo.isAppWeb) {
logger.warn('GetAppTiles does not work in an app web');
return null;
}
let r = GetJsonSync<{ value: IAppTile[]; }>(GetRestBaseUrl(siteUrl) + "/web/AppTiles?$filter=AppType%20eq%203&$select=Title,ProductId", null,
{ ...shortLocalCache, jsonMetadata: jsonTypes.nometadata });
return r.success && r.result && isNotEmptyArray(r.result.value) ? r.result.value.map(t => {
return {
Title: t.Title,
ProductId: normalizeGuid(t.ProductId)
} as IAppTile;
}) : [];
}
function GetWebType(WebTemplate: string): WebTypes {
return WebTemplate === "APP" ? WebTypes.App :
WebTemplate === "GROUP" ? WebTypes.Group :
WebTemplate === "STS" ? WebTypes.Team :
WebTypes.Other;
}
export async function GetServerTimeZone(siteUrl: string) {
siteUrl = GetSiteUrl(siteUrl);
let getTimeZoneUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone`;
let result = await GetJson<{
d: ITimeZone;
}>(getTimeZoneUrl, null, { ...longLocalCache });
if (result && result.d && !isNullOrUndefined(result.d)) {
return result.d;
}
else return null;
}
/**
* to be used when parsing string date to date object in JavaScript like so:
* var clientTimezoneOffset = new Date().getTimezoneOffset() * 60 * 1000;
* var clientDate = new Date(value);
* var serverDate = new Date(clientDate.getTime() + clientTimezoneOffset + GetServerTimeOffset);
* We must send a date in, since places like Israel have different offset for specific dates (GMT+2 or GMT+3)
* or just call SPServerLocalTimeToUTCDate
*/
async function GetServerTimeOffset(siteUrl: string, date: Date) {
siteUrl = GetSiteUrl(siteUrl);
let dateStr = toIsoDateFormat(date, { zeroTime: true, omitZ: true });
let inputDate = new Date(dateStr);
let getTimeZoneOffsetUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/localTimeToUTC(@date)?@date='${encodeURIComponent(dateStr)}'`;
let result = await GetJson<{ value: string; }>(getTimeZoneOffsetUrl, null, { ...weeekLongLocalCache, jsonMetadata: jsonTypes.nometadata });
if (result && !isNullOrEmptyString(result.value)) {
let resultDate = new Date(result.value.slice(0, result.value.length - 1));//remove Z and get as date.
return (resultDate.getTime() - inputDate.getTime());
}
else return 0;
}
/**
* to be used when parsing string date to date object in JavaScript like so:
* var clientTimezoneOffset = new Date().getTimezoneOffset() * 60 * 1000;
* var clientDate = new Date(value);
* var serverDate = new Date(clientDate.getTime() + clientTimezoneOffset + GetServerTimeOffset);
* We must send a date in, since places like Israel have different offset for specific dates (GMT+2 or GMT+3)
* or just call SPServerLocalTimeToUTCDate
*/
function GetServerTimeOffsetSync(siteUrl: string, date: Date) {
siteUrl = GetSiteUrl(siteUrl);
let dateStr = toIsoDateFormat(date, { zeroTime: true, omitZ: true });
let inputDate = new Date(dateStr);
let getTimeZoneOffsetUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/localTimeToUTC(@date)?@date='${encodeURIComponent(dateStr)}'`;
let result = GetJsonSync<{ value: string; }>(getTimeZoneOffsetUrl, null, { ...weeekLongLocalCache, jsonMetadata: jsonTypes.nometadata });
if (result && result.success && !isNullOrEmptyString(result.result.value)) {
let resultDate = new Date(result.result.value.slice(0, result.result.value.length - 1));//remove Z and get as date.
return (resultDate.getTime() - inputDate.getTime());
}
else return 0;
}
/** get date yyyy:MM:ddTHH:mm:ss NO ZED, or a date object created in the server local time, and return a date object of the corrected UTC time */
export async function SPServerLocalTimeToUTCDate(siteUrl: string, date: string | Date) {
//used in 7700
if (isNullOrEmptyString(date)) return null;
siteUrl = GetSiteUrl(siteUrl);
if (!isDate(date))
date = new Date(date);
let serverTimeOffset = await GetServerTimeOffset(siteUrl, date);
return _SPServerLocalTimeToUTCDate(date, serverTimeOffset);
}
/** get date yyyy:MM:ddTHH:mm:ss NO ZED, or a date object created in the server local time, and return a date object of the corrected UTC time */
export function SPServerLocalTimeToUTCDateSync(siteUrl: string, date: string | Date) {
//used in 7700
if (isNullOrEmptyString(date)) return null;
siteUrl = GetSiteUrl(siteUrl);
if (!isDate(date))
date = new Date(date);
let serverTimeOffset = GetServerTimeOffsetSync(siteUrl, date);
return _SPServerLocalTimeToUTCDate(date, serverTimeOffset);
}
function _SPServerLocalTimeToUTCDate(date: Date, serverTimeOffset: number) {
let localTimeOffset = date.getTimezoneOffset() * 60000;
return new Date(serverTimeOffset - localTimeOffset + date.getTime());
}
/** get date yyyy:MM:ddTHH:mm:ss NO ZED
* returns yyyy:MM:ddTHH:mm:ssZ
* expensive, but works. for faster bulk parsing use toIsoDateFormat(new Date(GetServerTimeOffset + date.getTime()))
* or: SPServerLocalTimeToUTCDate
*/
export async function SPServerLocalTimeToUTC(siteUrl: string, date: string | Date) {
siteUrl = GetSiteUrl(siteUrl);
if (isDate(date)) {
date = toIsoDateFormat(date, { omitZ: true });
}
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/localTimeToUTC(@date)?@date='${encodeURIComponent(date)}'`;
let result = await GetJson<{ value: string; }>(restUrl, null, { ...weeekLongLocalCache, jsonMetadata: jsonTypes.nometadata });
return result && result.value || null;
}
/**
* convert date in ISO format (yyyy:MM:ddTHH:mm:ss) or SPServerLocalTime (5/27/2020 11:34, 5-27-2020 11:34)
* returns date in ISO UTC (yyyy:MM:ddTHH:mm:ssZ)
* expensive, but works. for faster bulk parsing use toIsoDateFormat(new Date(GetServerTimeOffset + date.getTime()))
* or: SPServerLocalTimeToUTCDateSync
*/
export function SPServerLocalTimeToUTCSync(siteUrl: string, date: string | Date) {
siteUrl = GetSiteUrl(siteUrl);
if (isDate(date)) {
date = toIsoDateFormat(date, { omitZ: true });
}
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/localTimeToUTC(@date)?@date='${encodeURIComponent(date)}'`;
let result = GetJsonSync<{ value: string; }>(restUrl, null, { ...weeekLongLocalCache, jsonMetadata: jsonTypes.nometadata });
return result.success && result.result.value || null;
}
/** get utc date yyyy:MM:ddTHH:mm:ssZ
* returns yyyy:MM:ddTHH:mm:ss NO ZED
* expensive, but works. for faster bulk parsing use toIsoDateFormat(new Date(date.getTime()-GetServerTimeOffset,{omitZ:true}))
*/
export async function UTCToSPServerLocalTime(siteUrl: string, date: string | Date) {
siteUrl = GetSiteUrl(siteUrl);
if (isDate(date)) {
date = toIsoDateFormat(date);
}
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/utcToLocalTime(@date)?@date='${encodeURIComponent(date)}'`;
let result = await GetJson<{ value: string; }>(restUrl, null, { ...longLocalCache, jsonMetadata: jsonTypes.nometadata });
return result && result.value || null;
}
/** get utc date yyyy:MM:ddTHH:mm:ssZ
* returns yyyy:MM:ddTHH:mm:ss NO ZED
* expensive, but works. for faster bulk parsing use toIsoDateFormat(new Date(date.getTime()-GetServerTimeOffset,{omitZ:true}))
*/
export function UTCToSPServerLocalTimeSync(siteUrl: string, date: string | Date) {
siteUrl = GetSiteUrl(siteUrl);
if (isDate(date)) {
date = toIsoDateFormat(date);
}
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings/timeZone/utcToLocalTime(@date)?@date='${encodeURIComponent(date)}'`;
let result = GetJsonSync<{ value: string; }>(restUrl, null, { ...longLocalCache, jsonMetadata: jsonTypes.nometadata });
return result.success && result.result.value || null;
}
export function SPServerLocalTimeSync(siteUrl?: string) {
siteUrl = GetSiteUrl(siteUrl);
var clientNowServerDeltas = getGlobal<{ [url: string]: number; }>("ClientNowServerDeltas");
var clientNowServerDelta = clientNowServerDeltas[siteUrl];
var now = new Date();
if (isNullOrUndefined(clientNowServerDelta)) {
var local = UTCToSPServerLocalTimeSync(siteUrl, now.toISOString());
clientNowServerDelta = (+now - +(new Date(local)));
clientNowServerDeltas[siteUrl] = clientNowServerDelta;
}
var newdate = new Date(+now - clientNowServerDelta);
return toIsoDateFormat(newdate, { omitZ: true });
}
export async function SPServerLocalTime(siteUrl: string) {
siteUrl = GetSiteUrl(siteUrl);
var clientNowServerDeltas = getGlobal<{ [url: string]: number; }>("ClientNowServerDeltas");
var clientNowServerDelta = clientNowServerDeltas[siteUrl];
var now = new Date();
if (isNullOrUndefined(clientNowServerDelta)) {
var local = await UTCToSPServerLocalTime(siteUrl, now.toISOString());
clientNowServerDelta = (+now - +(new Date(local)));
clientNowServerDeltas[siteUrl] = clientNowServerDelta;
}
var newdate = new Date(+now - clientNowServerDelta);
return toIsoDateFormat(newdate, { omitZ: true });
}
export function GetContextWebInformationSync(siteUrl: string): IContextWebInformation {
var siteId: string = null;
if (_spPageContextInfo && _spPageContextInfo.isAppWeb) {
//inside an app web you can't get the contextinfo for any other site
siteUrl = _spPageContextInfo.webServerRelativeUrl;
siteId = _spPageContextInfo.siteId;
} else {
siteId = GetSiteIdSync(siteUrl);
if (isNullOrEmptyString(siteId)) {
return null;
}
}
let result = GetJsonSync<{ d: { GetContextWebInformation: IContextWebInformation; }; }>(`${GetRestBaseUrl(siteUrl)}/contextinfo`, null, {
method: "POST",
maxAge: 5 * 60,
includeDigestInPost: false,
allowCache: true,
postCacheKey: `GetContextWebInformation_${normalizeGuid(siteId)}`
});
if (result && result.success) {
return result.result.d.GetContextWebInformation;
} else {
return null;
}
}
function _getCustomActionsBaseRestUrl(siteUrl?: string, options: { listId?: string, actionId?: string } = {}) {
const { listId, actionId } = { ...options };
let restUrl = `${GetRestBaseUrl(siteUrl)}/web`;
if (!isNullOrEmptyString(listId)) {
restUrl += `/lists('${normalizeGuid(listId)}')`;
}
restUrl += `/UserCustomActions`;
if (!isNullOrEmptyString(actionId)) {
restUrl += `('${actionId}')`;
}
return restUrl;
}
function _parseCustomActionReponse(action: IUserCustomActionInfo) {
if (isNullOrUndefined(action)) {
return action;
}
if (!isNullOrUndefined(action.Rights)) {
if (isNumeric(action.Rights.High)) {
action.Rights.High = Number(action.Rights.High)
}
if (isNumeric(action.Rights.Low)) {
action.Rights.Low = Number(action.Rights.Low);
}
}
return action;
}
function _convertCustomActionToPostData(action: Omit<Partial<IUserCustomActionInfo>, "Id">) {
//The rest end point expects the rights in string format for some odd reason despite IBasePermissions being stored
//as High/Low numbers and the methods using numbers (ie. SPBasePermission). Even EffectiveBasePermissions on
//a list are stored using numbers.
let hasRights = !isNullOrUndefined(action.Rights);
let partialAction: {
Rights: {
High: string;
Low: string;
};
};
if (hasRights) {
partialAction = {
Rights: {
High: `${action.Rights.High}`,
Low: `${action.Rights.Low}`
}
};
delete action.Rights;
}
let data = { ...action, ...partialAction };
return data;
}
/** Get UserCustomActions for web/list */
export async function GetUserCustomActions(siteUrl: string, listId?: string, allowCache = true): Promise<IUserCustomActionInfo[]> {
let restUrl = _getCustomActionsBaseRestUrl(siteUrl, { listId: listId });
let cacheOptions = allowCache === true ? shortLocalCache : { allowCache: false };
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
...cacheOptions
};
try {
let response = await GetJson<{ value: IUserCustomActionInfo[]; }>(restUrl, null, restOptions);
if (!isNullOrUndefined(response) && !isNullOrEmptyArray(response.value)) {
return response.value.map(_parseCustomActionReponse);
}
} catch {
}
return [];
}
/** Get UserCustomAction by id from web/list */
export async function GetUserCustomActionById(siteUrl: string, customActionId: string, listId?: string, allowCache = true): Promise<IUserCustomActionInfo> {
let restUrl = _getCustomActionsBaseRestUrl(siteUrl, { listId: listId, actionId: customActionId });
let cacheOptions = allowCache === true ? shortLocalCache : { allowCache: false };
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
...cacheOptions
};
try {
let response = await GetJson<IUserCustomActionInfo>(restUrl, null, restOptions);
if (!isNullOrUndefined(response)) {
return _parseCustomActionReponse(response)
}
} catch {
}
return null;
}
/** Get UserCustomAction by name from web/list */
export async function GetUserCustomActionByName(siteUrl: string, name: string, listId?: string, allowCache = true): Promise<IUserCustomActionInfo[]> {
let restUrl = `${_getCustomActionsBaseRestUrl(siteUrl, { listId: listId })}?$filter=Name eq '${encodeURIComponent(name)}'`;
let cacheOptions = allowCache === true ? shortLocalCache : { allowCache: false };
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
...cacheOptions
};
try {
let response = await GetJson<{ value: IUserCustomActionInfo[]; }>(restUrl, null, restOptions);
if (!isNullOrUndefined(response) && !isNullOrEmptyArray(response.value)) {
return response.value.map(_parseCustomActionReponse);
}
} catch {
}
return [];
}
/** Add UserCustomAction to web/list */
export async function AddUserCustomAction(siteUrl: string, userCustomActionInfo: Omit<Partial<IUserCustomActionInfo>, "Id">, listId?: string): Promise<IUserCustomActionInfo> {
let restUrl = _getCustomActionsBaseRestUrl(siteUrl, { listId: listId });
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
method: "POST",
includeDigestInPost: true
};
try {
let data = _convertCustomActionToPostData(userCustomActionInfo);
let response = await GetJson<IUserCustomActionInfo>(restUrl, JSON.stringify(data), restOptions);
if (!isNullOrUndefined(response)) {
return _parseCustomActionReponse(response);
}
} catch {
}
return null;
}
/** Update UserCustomAction to web/list */
export async function UpdateUserCustomAction(siteUrl: string, customActionId: string, userCustomActionInfo: Omit<Partial<IUserCustomActionInfo>, "Id">, listId?: string): Promise<boolean> {
let restUrl = _getCustomActionsBaseRestUrl(siteUrl, { listId: listId, actionId: customActionId });
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
method: "POST",
xHttpMethod: "MERGE",
includeDigestInPost: true
};
try {
let data = _convertCustomActionToPostData(userCustomActionInfo);
let result = await GetJson<{ "odata.null": boolean } | string>(restUrl, JSON.stringify(data), restOptions);
return !isNullOrUndefined(result) && result["odata.null"] === true || isNullOrEmptyString(result);
} catch {
}
return false;
}
/** Delete UserCustomAction from web/list */
export async function DeleteUserCustomAction(siteUrl: string, customActionId: string, listId?: string): Promise<boolean> {
let restUrl = _getCustomActionsBaseRestUrl(siteUrl, { listId: listId, actionId: customActionId });
let restOptions: IRestOptions = {
jsonMetadata: jsonTypes.nometadata,
method: "POST",
xHttpMethod: "DELETE",
includeDigestInPost: true
};
try {
let result = await GetJson<{ "odata.null": boolean } | string>(restUrl, null, restOptions);
return !isNullOrUndefined(result) && result["odata.null"] === true || isNullOrEmptyString(result);
} catch {
}
return false;
}
/** Get web regional settings */
export async function GetRegionalSettings(siteUrl?: string) {
siteUrl = GetSiteUrl(siteUrl);
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/regionalSettings`;
try {
let result = await GetJson<IWebRegionalSettings>(restUrl, null, { ...mediumLocalCache, jsonMetadata: jsonTypes.nometadata });
return result;
} catch {
}
return null;
}
/** Get all web properties */
export async function GetAllWebProperties(siteUrl?: string) {
siteUrl = GetSiteUrl(siteUrl);
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/AllProperties`;
try {
let result = await GetJson<IDictionary<string>>(restUrl, null, { ...shortLocalCache, jsonMetadata: jsonTypes.nometadata });
return result;
} catch {
}
return null;
}
/** Get web property by name */
export async function GetWebPropertyByName(name: string, siteUrl?: string) {
siteUrl = GetSiteUrl(siteUrl);
let restUrl = `${GetRestBaseUrl(siteUrl)}/web/AllProperties?$select=${name}`;
try {
let result = await GetJson<IDictionary<string>>(restUrl, null, { ...shortLocalCache, jsonMetadata: jsonTypes.nometadata });
if (!isNullOrUndefined(result) && !isNullOrUndefined(result[name])) {
return result[name];
}
} catch {
}
return null;
}
export function getFormDigest(serverRelativeWebUrl?: string) {
var contextWebInformation = GetContextWebInformationSync(serverRelativeWebUrl);
return contextWebInformation && contextWebInformation.FormDigestValue || null;
}
export interface spfxContext { legacyPageContext: typeof _spPageContextInfo }
export function ensureLegacyProps(pageContext: spfxContext) {
try {
let isContextOk = (ctx: typeof _spPageContextInfo) => !isNullOrUndefined(ctx) && !isNullOrUndefined(ctx.webServerRelativeUrl);
let getLegacyContext = (ctx: spfxContext) => !isNullOrUndefined(ctx) && !isNullOrUndefined(ctx.legacyPageContext) ? ctx.legacyPageContext : null;
let getContext = (ctx: (typeof _spPageContextInfo) | spfxContext) => isContextOk(ctx as typeof _spPageContextInfo) ? ctx : getLegacyContext(ctx as spfxContext);
if (isTypeofFullNameNullOrUndefined("_spPageContextInfo") || !isContextOk(_spPageContextInfo)) {
logger.info(`_spPageContextInfo ${isTypeofFullNameNullOrUndefined("_spPageContextInfo") ? 'is missing' : 'is broken'}, wrapping with our property`);
//bug in SPFx during inplace left navigation will put an SPFx object into this global. Correct it using the setter.
let _currentContext = pageContext.legacyPageContext;
Object.defineProperty(window, "_spPageContextInfo", {
set: (newContext) =