xquery version "3.0";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)
import module namespace get         = "http://art-decor.org/ns/art-decor-settings" at "art-decor-settings.xqm";
import module namespace aduser      = "http://art-decor.org/ns/art-decor-users" at "../api/api-user-settings.xqm";
declare namespace sm            = "http://exist-db.org/xquery/securitymanager";
declare namespace config        = "http://exist-db.org/Configuration";
declare namespace json          = "http://www.json.org";

declare %private function local:userInfo($userName as xs:string, $userDisplayName as xs:string, $userEmail as xs:string?, $userGroups as element(groups)?, $active as xs:boolean) as element() {
    <user name="{$userName}" active="{$active}">
        <displayName>{$userDisplayName}</displayName>
    {
        if (get:strCurrentUserName()='guest') then () else (
            if (empty($userEmail)) then () else (
                <email>{$userEmail}</email>
            )
            ,
            if (sm:get-user-groups(get:strCurrentUserName())=$aduser:editGroups or $userName=get:strCurrentUserName()) then (
                $userGroups
            ) else ()
        )
    }
    </user>
};

(:
<search>
    <string/>
    <active/>
    <group/>
    <creation comp=""/>
    <lastlogin comp=""/>
    <lastemail comp=""/>
</search>
:)
let $searchParams           := 
    if (request:exists()) then request:get-data()/search else (
        <search>
            <active>true</active>
            <group>decor</group>
            <creation>2021-01-01</creation>
            <lastlogin></lastlogin>
            <lastemail></lastemail>
        </search>
    )
let $searchParams           := 
    if ($searchParams) then 
        $searchParams 
    else 
    if (request:exists()) then 
        <search><string>{request:get-parameter('searchString',())}</string></search> 
    else ()
let $searchString           := $searchParams/string/lower-case(.)[string-length() > 0]
let $searchActive           := $searchParams/active[string-length() > 0]
let $searchGroups           := $searchParams/group[string-length() > 0]/tokenize(., '\s')
let $searchCreation         := $searchParams/creation[string-length() > 0]
let $searchLastLogin        := $searchParams/lastlogin[string-length() > 0]
let $searchLastEmail        := $searchParams/lastemail[string-length() > 0]

(:
<json:value xmlns:json="http://www.json.org">
    <json:value>
        <user>SYSTEM</user>
        <fullName>SYSTEM</fullName>
        <description>System Internals</description>
        <password/>
        <disabled json:literal="true">false</disabled>
        <umask>18</umask>
        <groups json:array="true">dba</groups>
        <groups json:array="true">decor-admin</groups>
    </json:value>
    ...
</json:value>
:)
(: list-users() fails for non-dba users, i.e. must be logged in :)
(:  when you are e.g. an decor-admin user you still need access to the user list to add authors to a project. Fallback onto user-info.xml. 
    Caveat: people who have been added through the eXist-db user manager will NOT show up here...:)
let $accounts      :=
    try {
        if (sm:is-dba(get:strCurrentUserName())) then (
            if (empty($searchString)) then
                sm:list-users()
            else (
                sm:list-users()[contains(., $searchString)]
            )
        )
        else 
        if (sm:get-user-groups(get:strCurrentUserName())=$aduser:editGroups) then (
            aduser:getUserList($searchString)
        )
        else (
            (:error(QName('http://art-decor.org/ns/error', 'InsufficientPermissions'), concat('User ',get:strCurrentUserName(),' cannot request info for other users. User ',get:strCurrentUserName(),' must be a member of any of these groups: ',string-join($aduser:editGroups,' '),')')):)
            <json:value xmlns:json="http://www.json.org">
                <json:value>
                    <user>{get:strCurrentUserName()}</user>
                    <fullName>{get:strCurrentUserName()}</fullName>
                </json:value>
            </json:value>
        )
    }
    catch * {
        <json:value xmlns:json="http://www.json.org">
            <json:value>
                <user>guest</user>
                <fullName>guest</fullName>
            </json:value>
        </json:value>
    }
(: Return user details for all users except SYSTEM :)
let $accounts       := if ($accounts instance of element()) then ($accounts/json:value[not(user='SYSTEM')]) else $accounts[not(.='SYSTEM')]

let $userInfo       :=
    if ($accounts instance of element()) then (
        for $account in $accounts
        let $userName           := $account/user/string()
        let $userDisplayName    := 
            try {
                if ($account/fullName[not(.='')]) 
                then ($account/fullName/string())
                else (aduser:getUserDisplayName($userName))
            }
            catch * {$userName}
        let $userDisplayName    := if ($userDisplayName[not(.='')]) then $userDisplayName else $userName
        let $userEmail          := try { aduser:getUserEmail($userName) } catch * {()}
        let $userGroups         := <groups>{for $group in $account/groups return <group>{data($group)}</group>}</groups>
        let $userIsActive       := not($account/disabled = 'true')
        
        let $userMatches        := 
            if (empty($searchActive)) then true() else
            if ($userIsActive) then $searchActive = 'true' else $searchActive = 'false' 
        let $userMatches        :=
            if (empty($searchGroups)) then $userMatches else ($userMatches and exists($account/groups[. = $searchGroups]))
        let $userMatches        :=
            if (empty($searchLastLogin)) then $userMatches else (
                let $lastLogin  := aduser:getUserLastLoginTime($userName)
                
                return
                    if ($searchLastLogin castable as xs:date and $lastLogin castable as xs:date) then (
                        if ($searchLastLogin[@comp = '=']) then xs:date($lastLogin) = xs:date($searchLastLogin) else
                        if ($searchLastLogin[@comp = '>']) then xs:date($lastLogin) gt xs:date($searchLastLogin) else
                        if ($searchLastLogin[@comp = '>=']) then xs:date($lastLogin) ge xs:date($searchLastLogin) else
                        if ($searchLastLogin[@comp = '&lt;']) then xs:date($lastLogin) lt xs:date($searchLastLogin) else
                        if ($searchLastLogin[@comp = '&lt;=']) then xs:date($lastLogin) le xs:date($searchLastLogin) else (
                            false()
                        )
                    ) else ()
            )
        order by $userDisplayName
        return
            if ($userMatches) then local:userInfo($userName, $userDisplayName, $userEmail, $userGroups, $userIsActive) else ()
    ) else (
        for $account in $accounts
        let $userName           := $account
        let $userDisplayName    := try { aduser:getUserDisplayName($userName) } catch * {$userName}
        let $userDisplayName    := if ($userDisplayName[not(.='')]) then $userDisplayName else $userName
        let $userEmail          := try { aduser:getUserEmail($userName) } catch * {()}
        let $userGroups         := try { aduser:getUserInfo($userName)/groups } catch * {()}
        let $userIsActive       := aduser:isUserActive($userName)
        
        let $userMatches        := 
            if (empty($searchActive)) then true() else
            if ($userIsActive) then $searchActive = 'true' else $searchActive = 'false'
        let $userMatches        :=
            if (empty($searchGroups)) then $userMatches else ($userMatches and exists($userGroups/group[. = $searchGroups]))
        order by $userDisplayName
        return
            if ($userMatches) then local:userInfo($userName, $userDisplayName, $userEmail, $userGroups, $userIsActive) else ()
    )

return
<users current-user="{get:strCurrentUserName()}" current="{count($userInfo)}" total="{count($accounts)}">{$userInfo}</users>
