Accessing SXA Partial Design Datasources using Powershell

Accessing SXA Partial Design Datasources using Powershell

Page created using Sitecore SXA page designs and partial designs does not have all rendering information on its layout which is the purpose of the partial design to have more flexibility.

Page Composition in SXA

As you may know, in Sitecore SXA we can create multiple partial designs, for example, Header, Footer, etc. Afterward we need to create a page design and on the page design we define which partials should be referenced on that page design.

Taking a look at the mvc.getXmlBasedLayoutDefinition pipeline, you will notice the AddPartialDesignsRenderings and ResolveLocalDatasources processors:

<processor type="Sitecore.XA.Foundation.Presentation.Pipelines.GetXmlBasedLayoutDefinition.AddPartialDesignsRenderings, Sitecore.XA.Foundation.Presentation"
resolve="true" patch:source="Sitecore.XA.Foundation.Presentation.config" />

<processor type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.GetXmlBasedLayoutDefinition.ResolveLocalDatasources, Sitecore.XA.Foundation.LocalDatasources"
resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />

Basically the AddPartialDesignsRenderings does following:

  • find the page desing using IPresentationContext.GetDesignItem(contextItem)
  • Merge the partial design's rendering to the current page using ILayoutXmlService.MergePartialDesignsRenderings()

After page composition, the datasources needs to be resolved using ResolveLocalDatasources, and it is worth to have a look at resolveRenderingDatasource pipeline:

<resolveRenderingDatasource>
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.GetFromCache, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.QueryableDatasource, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.DatasourceFromField, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.CodeDatasource, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.PageRelativeDatasource, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor
        type="Sitecore.XA.Foundation.Search.Pipelines.ResolveRenderingDatasource.SearchDatasource, Sitecore.XA.Foundation.Search"
        resolve="true" patch:source="Sitecore.XA.Foundation.Search.config" />
    <processor
        type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.SetCache, Sitecore.XA.Foundation.LocalDatasources"
        resolve="true" patch:source="Sitecore.XA.Foundation.LocalDatasources.config" />
    <processor type="Spe.Integrations.Processors.ScriptedRenderingDataSourceResolve, Spe" patch:source="Spe.config" />
</resolveRenderingDatasource>

Getting partial designs using powershell

To access the rendering and datasource of the page, first we need to get all partial designs inlcuded on that specific page.
To access the partial designs of the page we need to use IPresentationContext.GetDesignItem like this:

# Returns the partial design items of the page
function Get-PagePartialDesigns {
    [CmdletBinding()]
    param(        
        [Parameter(Mandatory = $true, Position = 0)]
        [Item]$RootItem # Page Item
    )

    begin {
        Write-Verbose "Cmdlet Get-PagePartialDesigns - Begin"
        Import-Function Test-InDelegatedArea
    }
    
    process {
        Write-Verbose "Cmdlet Get-PagePartialDesigns - Process"
        $datasourceItems = New-Object System.Collections.ArrayList

        if ((Test-InDelegatedArea $RootItem)) {
            Write-Host "You can not access the delegated page"
            retrun $datasourceItems;
        }
        $instance = [Sitecore.DependencyInjection.ServiceLocator]::ServiceProvider
        $presentation = $instance.GetType().GetMethod('GetService').Invoke($instance, [Sitecore.XA.Foundation.Presentation.IPresentationContext])
        $partialDesigns = $presentation.GetDesignItem($RootItem)["PartialDesigns"].Split("|", [System.StringSplitOptions]::RemoveEmptyEntries)

        $lang = $RootItem.Language.name
        foreach ($partial in $partialDesigns) {
            if (-not([string]::IsNullOrWhiteSpace($partial))) {
                $partialPageItem = Get-Item -Id $partial -Path master: -lang $lang
                if ($null -ne $partialPageItem) {
                    [void]$datasourceItems.Add($partialPageItem)
                }
            }
        }

        return , $datasourceItems
    }

    end {
        Write-Verbose "Cmdlet Get-PagePartialDesigns - End"
    }
}

This function will return the partial design items used on the page.

Resolve Rendering datasources using Powershell

Using Get-LayoutDevice and Get-Rendering, we can access the datasources like this:

# Returns the Renderings of the given root item
function Get-RenderingDatasources {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [Item]$RootItem
    )

    begin {
        Write-Verbose "Cmdlet Add-PageLanguageVersion - Begin"
        Import-Function Test-InDelegatedArea
    }
    
    process {
        Write-Verbose "Cmdlet Add-PageLanguageVersion - Process"
        $datasourceItems = New-Object System.Collections.ArrayList

        if ((Test-InDelegatedArea $RootItem)) {
            Write-Host "You can not access the RenderingDatasources of delegated page"
            retrun $datasourceItems;
        }
        $instance = [Sitecore.DependencyInjection.ServiceLocator]::ServiceProvider
        $defaultLayout = Get-LayoutDevice -Default 
        $pageDataSources = Get-Rendering -Item $rootItem -Device $defaultLayout -FinalLayout
        
        foreach ($datasource in $pageDataSources) {
            if ($datasource.Datasource) {
                $rds = $datasource.Datasource
                
                # Return the raw Datasource to be parsed against the correct item
                [void]$datasourceItems.Add($rds)
            }
        }

        return , $datasourceItems
    }

    end {
        Write-Verbose "Cmdlet Add-PageLanguageVersion - End"
    }
}

And now we can bring all together:

# Returns the datasources used on the page
function Get-PageDatasources {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [Item]$RootItem,

        [Parameter(Mandatory = $false, Position = 1)]
        [switch]$includePartialDatasources
    )
    begin {
        Write-Verbose "Cmdlet Get-PageDatasources - Begin"
        Import-Function Test-InDelegatedArea
        Import-Function Get-PagePartialDesigns
        Import-Function Get-RenderingDatasources
    }

    process {
        Write-Verbose "Cmdlet Get-PageDatasources - Process"
        $datasourceItems = New-Object System.Collections.ArrayList

        if ((Test-InDelegatedArea $RootItem)) {
            Write-Host "You can not access the delegated page"
            retrun $datasourceItems;
        }
        
        # Get renderings that directly are on the page
        $directRenderings = Get-RenderingDatasources -RootItem $RootItem
        $instance = [Sitecore.DependencyInjection.ServiceLocator]::ServiceProvider

        if ($directRenderings.length) {
            foreach ($raw in $directRenderings) {
                $context = $instance.GetType().GetMethod('GetService').Invoke($instance, [Sitecore.XA.Foundation.Abstractions.IContext])
                $context.Item = $rootItem
                # Resolving the raw datasource based on the current page
                $ds = [Sitecore.Pipelines.ParseDataSource.ParseDataSourcePipeline]::Run($rootItem.Database, $raw, $rootItem)
                [void]$datasourceItems.Add($ds)

            }
        }

        if ($includePartialDatasources) {
            $partials = Get-PagePartialDesigns -RootItem $RootItem
            foreach ($partial in $partials) {
                $renderings = Get-RenderingDatasources -RootItem $partial
                foreach ($raw in $renderings) {
                    $context = $instance.GetType().GetMethod('GetService').Invoke($instance, [Sitecore.XA.Foundation.Abstractions.IContext])
                    $context.Item = $rootItem
                    # Resolving the raw datasource of the partial designs based on the current page
                    $ds = [Sitecore.Pipelines.ParseDataSource.ParseDataSourcePipeline]::Run($rootItem.Database, $raw, $rootItem)
                    [void]$datasourceItems.Add($ds)
                }
            }
        }

        return $datasourceItems
    }

    end {
        Write-Verbose "Cmdlet Get-PageDatasources - End"
    }
}

The trick is, when you want to solve the datasources of the partial desins, you will treat them as a normal sitecore page, and change the context to the partial design:

$renderings = Get-RenderingDatasources -RootItem $partial
$context = $instance.GetType().GetMethod('GetService').Invoke($instance, [Sitecore.XA.Foundation.Abstractions.IContext])
$context.Item = $rootItem
# Resolving the raw datasource of the partial designs based on the current page
$ds = [Sitecore.Pipelines.ParseDataSource.ParseDataSourcePipeline]::Run($rootItem.Database, $raw, $rootItem)