import { ISPHttpClientOptions , SPHttpClient } from '@microsoft/sp-http' ; import { Environment , EnvironmentType } from '@microsoft/sp-core-library' ; import { BaseComponentContext } from '@microsoft/sp-component-base' ; import { MockUsers , PeoplePickerMockClient } from './PeoplePickerMockClient' ; import { PrincipalType , IPeoplePickerUserItem } from "../PeoplePicker" ; import { IUsers , IUserInfo } from "../controls/peoplepicker/IUsers" ; import { cloneDeep , findIndex } from "@microsoft/sp-lodash-subset" ; import { sp } from '@pnp/sp' ; import "@pnp/sp/sputilities" ; import { Web } from "@pnp/sp/webs" ; import "@pnp/sp/webs" ; import "@pnp/sp/site-users/web" ; /**
 * Service implementation to search people in SharePoint
 */ export default class SPPeopleSearchService { private cachedPersonas : { [ property ] : IUserInfo [ ] ; } ; private cachedLocalUsers : { [ siteUrl ] : IUserInfo [ ] ; } ; /**
     * Service constructor
     */ constructor ( private context ) { this . cachedPersonas = { } ; this . cachedLocalUsers = { } ; this . cachedLocalUsers [ this . context . pageContext . web . absoluteUrl ] = [ ] ; sp . setup ( this . context ) ; } param value public generateUserPhotoLink ( value ) { return `${ this . context . pageContext . web . absoluteUrl }/_layouts/15/userphoto.aspx?accountname=${ encodeURIComponent ( value ) }&size=M` ; } param principalTypes public getSumOfPrincipalTypes ( principalTypes ) { return ! ! principalTypes && principalTypes . length > 0 ? principalTypes . reduce ( ( a , b ) => a + b , 0 ) : 1 ; } param groupName param siteUrl public async getGroupId ( groupName , siteUrl = null ) { if ( Environment . type === EnvironmentType . Local ) { return 1 ; } else { const groups = await this . searchTenant ( siteUrl , groupName , 1 , [ PrincipalType . SharePointGroup ] , false , 0 ) ; return ( groups && groups . length > 0 ) ? parseInt ( groups [ 0 ] . id ) : null ; } } /**
     * Search person by its email or login name
     */ public async searchPersonByEmailOrLogin ( email , principalTypes , siteUrl = null , groupId = null , ensureUser = false ) { if ( Environment . type === EnvironmentType . Local ) { const mockUsers = await this . searchPeopleFromMock ( email ) ; return ( mockUsers && mockUsers . length > 0 ) ? mockUsers [ 0 ] : null ; } else { const userResults = await this . searchTenant ( siteUrl , email , 1 , principalTypes , ensureUser , groupId ) ; return ( userResults && userResults . length > 0 ) ? userResults [ 0 ] : null ; } } /**
     * Search All Users from the SharePoint People database
     */ public async searchPeople ( query , maximumSuggestions , principalTypes , siteUrl = null , groupId = null , ensureUser = false ) { if ( Environment . type === EnvironmentType . Local ) { return this . searchPeopleFromMock ( query ) ; } else { return await this . searchTenant ( siteUrl , query , maximumSuggestions , principalTypes , ensureUser , groupId ) ; } } /**
     * Local site search
     */ private async localSearch ( siteUrl , query , principalTypes , showHiddenInUI , groupName , exactMatch = false ) { try { let stringVal = "" ; let cachedPropertyName = null ; if ( groupName ) { stringVal = `/_api/web/sitegroups/GetByName('${ groupName }')/users` ; cachedPropertyName = `${ siteUrl }-${ groupName }` ; } else { stringVal = "/_api/web/siteusers" ; cachedPropertyName = siteUrl ; } if ( typeof this . cachedPersonas [ cachedPropertyName ] === "undefined" ) { let filterVal = "" ; if ( principalTypes ) { filterVal = `?$filter=(${ principalTypes . map ( principalType => `PrincipalType eq ${ principalType }` ) . join ( " or " ) })` ; } filterVal = filterVal ? `${ filterVal } and (IsHiddenInUI eq ${ showHiddenInUI })` : `?$filter=IsHiddenInUI eq ${ showHiddenInUI }` ; const restApi = `${ siteUrl }${ stringVal }${ filterVal }` ; const data = await this . context . spHttpClient . get ( restApi , SPHttpClient . configurations . v1 , { headers : { 'Accept' : 'application/json;odata.metadata=none' } } ) ; if ( data . ok ) { const userDataResp = await data . json ( ) ; if ( userDataResp && userDataResp . value && userDataResp . value . length > 0 ) { this . cachedPersonas [ cachedPropertyName ] = cloneDeep ( userDataResp . value ) ; } } } if ( this . cachedPersonas [ cachedPropertyName ] ) { let persons = this . cachedPersonas [ cachedPropertyName ] ; if ( query ) { if ( exactMatch ) { persons = persons . filter ( element => element . Email . toLowerCase ( ) === query . toLowerCase ( ) || element . LoginName . toLowerCase ( ) === query . toLowerCase ( ) ) ; } else { persons = persons . filter ( element => element . Title . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 || element . Email . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 || element . LoginName . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 ) ; } } return persons . map ( item => ( { id : item . Id . toString ( ) , imageUrl : item . PrincipalType === PrincipalType . User ? this . generateUserPhotoLink ( item . Email ) : null , imageInitials : this . getFullNameInitials ( item . Title ) , text : item . Title , secondaryText : item . Email || item . LoginName , tertiaryText : "" , optionalText : "" } as IPeoplePickerUserItem ) ) ; } return [ ] ; } catch ( e ) { console . error ( "PeopleSearchService::localSearch: error occured while fetching the users." ) ; return [ ] ; } } /**
     * Tenant search
     */ private async searchTenant ( siteUrl , query , maximumSuggestions , principalTypes , ensureUser , groupId ) { try { const userRequestUrl = `${ siteUrl || this . context . pageContext . web . absoluteUrl }/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser` ; const searchBody = { queryParams : { AllowEmailAddresses : true , AllowMultipleEntities : false , AllUrlZones : false , MaximumEntitySuggestions : maximumSuggestions , PrincipalSource : 15 , PrincipalType : this . getSumOfPrincipalTypes ( principalTypes ) , QueryString : query } } ; if ( siteUrl ) { searchBody . queryParams [ "SharePointGroupID" ] = 0 ; } if ( groupId && typeof ( groupId ) === 'number' ) { searchBody . queryParams [ "SharePointGroupID" ] = groupId ; } else if ( groupId && typeof ( groupId ) === 'string' ) { const graphUserRequestUrl = `/groups/${ groupId }/members?$count=true&$search="displayName:${ query }" OR "mail:${ query }"` ; const graphClient = await this . context . msGraphClientFactory . getClient ( ) ; const graphUserResponse = await graphClient . api ( graphUserRequestUrl ) . header ( 'ConsistencyLevel' , 'eventual' ) . get ( ) ; if ( graphUserResponse . value && graphUserResponse . value . length > 0 ) { const _users = [ ] ; const batch = Web ( this . context . pageContext . web . absoluteUrl ) . createBatch ( ) ; for ( const value of graphUserResponse . value ) { sp . web . inBatch ( batch ) . ensureUser ( value . userPrincipalName ) . then ( u => _users . push ( u . data ) ) ; } await batch . execute ( ) ; let userResult = [ ] ; for ( const user of _users ) { userResult . push ( { id : ensureUser ? user . Id : user . LoginName , loginName : user . LoginName , imageUrl : this . generateUserPhotoLink ( user . Email ) , imageInitials : this . getFullNameInitials ( user . Title ) , text : user . Title , secondaryText : user . Email , tertiaryText : '' , optionalText : '' } ) ; } return userResult ; } return [ ] ; } const httpPostOptions = { headers : { 'accept' : 'application/json' , 'content-type' : 'application/json' } , body : JSON . stringify ( searchBody ) } ; const data = await this . context . spHttpClient . post ( userRequestUrl , SPHttpClient . configurations . v1 , httpPostOptions ) ; if ( data . ok ) { const userDataResp = await data . json ( ) ; if ( userDataResp && userDataResp . value && userDataResp . value . length > 0 ) { let values = userDataResp . value ; if ( typeof userDataResp . value === "string" ) { values = JSON . parse ( userDataResp . value ) ; } values = values . filter ( v => ! ( v . EntityData && v . EntityData . PrincipalType && v . EntityData . PrincipalType === "UNVALIDATED_EMAIL_ADDRESS" ) ) ; if ( ensureUser ) { for ( const value of values ) { if ( ! value . EntityData || ( value . EntityData && typeof value . EntityData . SPGroupID === "undefined" ) ) { const id = await this . ensureUser ( value . Key ) ; value . LoginName = value . Key ; value . Key = id ; } } } values = values . filter ( v => v . Key !== null ) ; const userResults = values . map ( element => { switch ( element . EntityType ) { case 'User' : const accountName = element . Description || "" ; const email = element . EntityData ?. Email || element . Description ; return { id : element . Key , loginName : element . LoginName ? element . LoginName : element . Key , imageUrl : this . generateUserPhotoLink ( accountName ) , imageInitials : this . getFullNameInitials ( element . DisplayText ) , text : element . DisplayText , secondaryText : email , tertiaryText : "" , optionalText : "" } as IPeoplePickerUserItem ; case 'SecGroup' : const secondaryText = element . EntityData ?. Email || element . ProviderName ; return { id : element . Key , loginName : element . LoginName ? element . LoginName : element . Key , imageInitials : this . getFullNameInitials ( element . DisplayText ) , text : element . DisplayText , secondaryText , } as IPeoplePickerUserItem ; case 'FormsRole' : return { id : element . Key , loginName : element . LoginName ? element . LoginName : element . Key , imageInitials : this . getFullNameInitials ( element . DisplayText ) , text : element . DisplayText , secondaryText : element . ProviderName } as IPeoplePickerUserItem ; default : return { id : element . EntityData . SPGroupID , loginName : element . EntityData . AccountName , imageInitials : this . getFullNameInitials ( element . DisplayText ) , text : element . DisplayText , secondaryText : element . EntityData . AccountName } as IPeoplePickerUserItem ; } } ) ; return userResults ; } } return [ ] ; } catch ( e ) { console . error ( "PeopleSearchService::searchTenant: error occured while fetching the users." ) ; return [ ] ; } } param userId private async ensureUser ( userId ) { const siteUrl = this . context . pageContext . web . absoluteUrl ; if ( this . cachedLocalUsers && this . cachedLocalUsers [ siteUrl ] ) { const users = this . cachedLocalUsers [ siteUrl ] ; const userIdx = findIndex ( users , u => u . LoginName === userId ) ; if ( userIdx !== - 1 ) { return users [ userIdx ] . Id ; } } const restApi = `${ siteUrl }/_api/web/ensureuser` ; const data = await this . context . spHttpClient . post ( restApi , SPHttpClient . configurations . v1 , { body : JSON . stringify ( { 'logonName' : userId } ) } ) ; if ( data . ok ) { const user = await data . json ( ) ; if ( user && user . Id ) { this . cachedLocalUsers [ siteUrl ] . push ( user ) ; return user . Id ; } } return null ; } /**
     * Generates Initials from a full name
     */ private getFullNameInitials ( fullName ) { if ( fullName === null ) { return fullName ; } const words = fullName . split ( ' ' ) ; if ( words . length === 0 ) { return '' ; } else if ( words . length === 1 ) { return words [ 0 ] . charAt ( 0 ) ; } else { return ( words [ 0 ] . charAt ( 0 ) + words [ 1 ] . charAt ( 0 ) ) ; } } /**
     * Gets the user photo url
     */ private getUserPhotoUrl ( userEmail , siteUrl ) { return `${ siteUrl }/_layouts/15/userphoto.aspx?size=S&accountname=${ userEmail }` ; } /**
     * Returns fake people results for the Mock mode
     */ private searchPeopleFromMock ( query ) { let mockClient = new PeoplePickerMockClient ( ) ; let filterValue = { valToCompare : query } ; return new Promise < Array < IPeoplePickerUserItem > > ( ( resolve ) => resolve ( MockUsers . filter ( mockClient . filterPeople , filterValue ) ) ) ; } }