core/modules/monkeyhtml/private/Card/New-HtmlCard.ps1

# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Function New-HtmlCard{
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .NOTES
            Author : Juan Garrido
            Twitter : @tr1ana
            File Name : New-HtmlCard
            Version : 1.0
 
        .LINK
            https://github.com/silverhack/monkey365
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope='Function')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Xml.XmlDocument])]
    Param (
        [parameter(Mandatory= $false, HelpMessage= "Class name")]
        [String[]]$ClassName,

        [parameter(Mandatory= $false, HelpMessage= "ID card")]
        [String]$Id,

        [parameter(Mandatory= $false, HelpMessage= "Style card")]
        [String]$Style,

        [parameter(Mandatory= $false, HelpMessage= "Img card")]
        [Switch]$ImgCard,

        [parameter(Mandatory= $false, HelpMessage= "Card Header")]
        [String]$Header,

        [parameter(Mandatory= $false, HelpMessage= "Card Header class")]
        [String[]]$HeaderClass,

        [parameter(Mandatory= $false, HelpMessage= "Header Object")]
        [AllowNull()]
        [Object]$HeaderObject,

        [parameter(Mandatory= $false, HelpMessage= "Card title")]
        [String]$Title,

        [parameter(Mandatory= $false, HelpMessage= "Card subtitle class")]
        [String[]]$TitleClass,

        [parameter(Mandatory= $false, HelpMessage= "Card subtitle")]
        [String]$SubTitle,

        [parameter(Mandatory= $false, HelpMessage= "Card subtitle class")]
        [String[]]$SubTitleClass,

        [parameter(Mandatory= $false, HelpMessage= "Card text")]
        [String]$CardText,

        [parameter(Mandatory= $false, HelpMessage= "Body class name")]
        [String[]]$BodyClass,

        [parameter(Mandatory= $false, HelpMessage= "Body Object")]
        [AllowNull()]
        [Object]$BodyObject,

        [parameter(Mandatory= $false, HelpMessage= "Footer text")]
        [String]$FooterText,

        [parameter(Mandatory= $false, HelpMessage= "Footer Object")]
        [AllowNull()]
        [Object]$FooterObject,

        [parameter(Mandatory= $false, HelpMessage= "Card footer class")]
        [String[]]$FooterClass,

        [parameter(Mandatory= $false, HelpMessage= "Collapsible Card")]
        [Switch]$Collapsible,

        [parameter(Mandatory= $false, HelpMessage= "Template")]
        [System.Xml.XmlDocument]$Template
    )
    dynamicparam{
        # set a new dynamic parameter
        $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
        #Add parameters for Microsoft365 instance
        if($null -ne (Get-Variable -Name ImgCard -ErrorAction Ignore)){
            #Create the -ImgSrc string parameter
            $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            # define a new parameter attribute
            $_attr_name = New-Object System.Management.Automation.ParameterAttribute
            $_attr_name.Mandatory = $true
            $attributeCollection.Add($_attr_name)
            $_pname = 'ImgSrc'
            $_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($_pname,
            [String], $attributeCollection)
            $paramDictionary.Add($_pname, $_type_dynParam)
        }
        # return the collection of dynamic parameters
        $paramDictionary
    }
    Begin{
        If($PSBoundParameters.ContainsKey('Template') -and $PSBoundParameters['Template']){
            $TemplateObject = $PSBoundParameters['Template']
        }
        ElseIf($null -ne (Get-Variable -Name Template -Scope Script -ErrorAction Ignore)){
            $TemplateObject = $script:Template
        }
        Else{
            [xml]$TemplateObject = "<html></html>"
        }
        #main card
        $card = [xml] '<div class="card"></div>'
        #Add card header
        $cardHeader = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'div', $TemplateObject.NamespaceURI);
        #Add extra class to header
        If($PSBoundParameters.ContainsKey('HeaderClass') -and $PSBoundParameters['HeaderClass']){
            $_Class = [String]::Join(' ',$HeaderClass);
            $div_class = ("card-header {0}" -f $_Class)
            [void]$cardHeader.SetAttribute('class',$div_class)
        }
        Else{
            [void]$cardHeader.SetAttribute('class',"card-header")
        }
        #Add card body
        $cardBody = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'div', $TemplateObject.NamespaceURI);
        #Add extra class to body
        If($PSBoundParameters.ContainsKey('BodyClass') -and $PSBoundParameters['BodyClass']){
            $_Class = [String]::Join(' ',$BodyClass);
            $div_class = ("card-body {0}" -f $_Class)
            [void]$cardBody.SetAttribute('class',$div_class)
        }
        Else{
            [void]$cardBody.SetAttribute('class',"card-body")
        }
        #Add card footer
        $cardFooter = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'div', $TemplateObject.NamespaceURI);
        #Add extra class to footer
        If($PSBoundParameters.ContainsKey('FooterClass') -and $PSBoundParameters['FooterClass']){
            $_Class = [String]::Join(' ',$FooterClass);
            $div_class = ("card-footer {0}" -f $_Class)
            [void]$cardFooter.SetAttribute('class',$div_class)
        }
        Else{
            [void]$cardFooter.SetAttribute('class',"card-footer")
        }
    }
    Process{
        #Set Class name
        If($ClassName){
            $main_node = $card.SelectSingleNode('//div[@class="card"]')
            $_Class = [String]::Join(' ',$ClassName);
            $div_class = ("card {0}" -f $_Class)
            [void]$main_node.SetAttribute('class',$div_class)
        }
        #Set ID card
        If($Id){
            $main_node = $card.SelectSingleNode('//div[contains(@class,"card")]')
            [void]$main_node.SetAttribute('id',$Id)
        }
        #Check if custom style should be added to card
        if($Style){
            $main_node = $card.SelectSingleNode('//div[contains(@class,"card")]')
            [void]$main_node.SetAttribute('style',$Style.ToString())
        }
        #Check if Card img
        If($PSBoundParameters.ContainsKey('ImgCard') -and $PSBoundParameters['ImgCard'].IsPresent){
            $img_element = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'img', $TemplateObject.NamespaceURI);
            [void]$img_element.SetAttribute('class','card-img-top')
            [void]$img_element.SetAttribute('src',$PSBoundParameters['ImgSrc'])
            #Close Img
            [void]$img_element.AppendChild($TemplateObject.CreateWhitespace(""))
            #Add img to the main div
            $main_node = $card.SelectSingleNode('//div[contains(@class,"card")]')
            If($main_node){
                [void]$main_node.AppendChild($img_element)
            }
        }
        #Check if Header object
        If($PSBoundParameters.ContainsKey('HeaderObject')){
            If($HeaderObject -is [System.Xml.XmlDocument]){
                Write-Verbose ($Script:messages.AppendDocElementTo -f "Card header")
                [void]$cardHeader.AppendChild($TemplateObject.ImportNode($HeaderObject.get_DocumentElement(), $True))
            }
            ElseIf($HeaderObject -is [System.Xml.XmlElement]){
                Write-Verbose ($Script:messages.AppendXmlElementTo -f "Card header")
                [void]$cardHeader.AppendChild($TemplateObject.ImportNode($HeaderObject,$True))
            }
            ElseIf($null -eq $HeaderObject){
                Write-Verbose ($Script:messages.EmptyDiv -f "Card header")
                #set blank
                [void]$cardHeader.AppendChild($TemplateObject.CreateTextNode([string]::Empty))
            }
            Else{
                #Add text
                [void]$cardHeader.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['HeaderObject'].ToString()))
            }
        }
        #Check if Card Header
        If($PSBoundParameters.ContainsKey('Header') -and $PSBoundParameters['Header']){
            [void]$cardHeader.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['Header'].ToString()))
        }
        #Check if Card title
        If($PSBoundParameters.ContainsKey('Title') -and $PSBoundParameters['Title']){
            $h5Title = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'h5', $TemplateObject.NamespaceURI);
            If($PSBoundParameters.ContainsKey('TitleClass') -and $PSBoundParameters['TitleClass']){
                $_Class = [String]::Join(' ',$TitleClass);
                $div_class = ("card-title {0}" -f $_Class)
                [void]$h5Title.SetAttribute('class',$div_class)
            }
            Else{
                [void]$h5Title.SetAttribute('class','card-title')
            }
            #Add title
            [void]$h5Title.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['Title'].ToString()))
            #Add to body
            [void]$cardBody.AppendChild($h5Title)
        }
        #Check if Card subtitle
        If($PSBoundParameters.ContainsKey('SubTitle') -and $PSBoundParameters['SubTitle']){
            $h6SubTitle = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'h6', $TemplateObject.NamespaceURI);
            If($PSBoundParameters.ContainsKey('SubTitleClass') -and $PSBoundParameters['SubTitleClass']){
                $_Class = [String]::Join(' ',$SubTitleClass);
                $div_class = ("card-subtitle {0}" -f $_Class)
                [void]$h6SubTitle.SetAttribute('class',$div_class)
            }
            Else{
                [void]$h6SubTitle.SetAttribute('class','card-subtitle')
            }
            #Add title
            [void]$h6SubTitle.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['SubTitle'].ToString()))
            #Add to body
            [void]$cardBody.AppendChild($h6SubTitle)
        }
        #Check if Card text
        If($PSBoundParameters.ContainsKey('CardText') -and $PSBoundParameters['CardText']){
            $pText = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'p', $TemplateObject.NamespaceURI);
            [void]$pText.SetAttribute('class','card-text')
            #Add text
            [void]$pText.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['CardText'].ToString()))
            #Add to body
            [void]$cardBody.AppendChild($pText)
        }
        #Check if Body object
        If($PSBoundParameters.ContainsKey('BodyObject')){
            If($BodyObject -is [System.Xml.XmlDocument]){
                Write-Verbose ($Script:messages.AppendDocElementTo -f "Card body")
                [void]$cardBody.AppendChild($TemplateObject.ImportNode($BodyObject.get_DocumentElement(), $True))
            }
            ElseIf($BodyObject -is [System.Xml.XmlElement]){
                Write-Verbose ($Script:messages.AppendXmlElementTo -f "Card body")
                [void]$cardBody.AppendChild($TemplateObject.ImportNode($BodyObject,$True))
            }
            ElseIf($null -eq $BodyObject){
                Write-Verbose ($Script:messages.EmptyDiv -f "Card body")
                #set blank
                [void]$cardBody.AppendChild($TemplateObject.CreateTextNode([string]::Empty))
            }
            Else{
                #Add text
                [void]$cardBody.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['BodyObject'].ToString()))
            }
        }
        #Check if Card text
        If($PSBoundParameters.ContainsKey('FooterText') -and $PSBoundParameters['FooterText']){
            #Add text
            [void]$cardFooter.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['FooterText'].ToString()))
        }
        #Check if footer object
        If($PSBoundParameters.ContainsKey('FooterObject')){
            If($FooterObject -is [System.Xml.XmlDocument]){
                Write-Verbose ($Script:messages.AppendDocElementTo -f "Card footer")
                [void]$cardFooter.AppendChild($TemplateObject.ImportNode($FooterObject.get_DocumentElement(), $True))
            }
            ElseIf($FooterObject -is [System.Xml.XmlElement]){
                Write-Verbose ($Script:messages.AppendXmlElementTo -f "Card footer")
                [void]$cardFooter.AppendChild($TemplateObject.ImportNode($FooterObject,$True))
            }
            ElseIf($null -eq $FooterObject){
                Write-Verbose ($Script:messages.EmptyDiv -f "Card footer")
                #set blank
                [void]$cardFooter.AppendChild($TemplateObject.CreateTextNode([string]::Empty))
            }
            Else{
                #Add text
                [void]$cardFooter.AppendChild($TemplateObject.CreateTextNode($PSBoundParameters['FooterObject'].ToString()))
            }
        }
    }
    End{
        #Import node
        $card = $TemplateObject.ImportNode($card.DocumentElement,$true);
        #Check if collapsible card
        If($PSBoundParameters.ContainsKey('Collapsible') -and $PSBoundParameters['Collapsible'].IsPresent){
            If($cardHeader.HasChildNodes){
                $newId = ("Monkey{0}" -f [System.Guid]::NewGuid().Guid)
                #Add data-bs-toggle and data-bs-target
                [void]$cardHeader.SetAttribute('data-bs-toggle','collapse');
                #Add data-bs-toggle and data-bs-target
                [void]$cardHeader.SetAttribute('data-bs-target',("#{0}") -f $newId);
                #Add aria-controls
                [void]$cardHeader.SetAttribute('aria-controls',("{0}") -f $newId);
                #Set new div for Body
                $collapsibleDiv = $TemplateObject.CreateNode([System.Xml.XmlNodeType]::Element, $TemplateObject.Prefix, 'div', $TemplateObject.NamespaceURI);
                [void]$collapsibleDiv.SetAttribute('class',"collapse")
                [void]$collapsibleDiv.SetAttribute('id',$newId);
                #Add always body
                [void]$collapsibleDiv.AppendChild($cardBody);
                #Check if footer has childobjects
                If($cardFooter.HasChildNodes){
                    [void]$collapsibleDiv.AppendChild($cardFooter);
                }
                #Add header to card
                [void]$card.AppendChild($cardHeader)
                #Add div to card
                [void]$card.AppendChild($collapsibleDiv)
            }
            Else{
                Write-Warning "Unable to convert card to collapsible card. Header was not found"
            }
        }
        Else{
            #Check if cardHeader must be added
            If($cardHeader.HasChildNodes){
                #Add to card
                [void]$card.AppendChild($cardHeader)
            }
            #Check if cardBody must be added
            If($cardBody.HasChildNodes){
                #Add to card
                [void]$card.AppendChild($cardBody)
            }
            #Check if cardFooter must be added
            If($cardFooter.HasChildNodes){
                #Add to card
                [void]$card.AppendChild($cardFooter)
            }
        }
        #Close potentially divs
        $allDivs = $card.SelectNodes("//div")
        Foreach($div in $allDivs){
            If($div.HasChildNodes -eq $false){
                $div.InnerText = [string]::Empty
            }
        }

        #return Card
        return $card
    }
}