Sitecore Platform Inspection – Part 1

Sometimes our Sitecore instance suffers from performance issues, cumbersome content managing process, limitations, etc. so there is a need we need to review our solution and evaluate how well it is implemented. In these blog series I will provide some guidance on how to perform a  Sitecore platform inspection to evaluate the architecture of a solution measured against current Sitecore best practices

I will use Sitecore PowerShell Extensions module to execute scripts to get interesting information from templates and content items so we can add more value to the report.

We need to inspect how well the current solution has been implemented and if the Sitecore architecture is in good shape. In order to evaluate the architecture there are some areas we need to focus:

  • Data Templates
  • Content Structure
  • Security Roles and Users

There are other areas but these are the ones I will describe in these series. 

Data Templates

In this topic, we review how the templates are created, how well they are structured and if they satisfy the Sitecore Helix principles.  

Avoid duplicated field names in the same template or shared base. We should be careful to This is not recommended since the template will have two field names and it could produce unexpected results

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db)
foreach ($sourceItem in $templates) {
   $fields = $sourceItem.Value.GetFields() | group-object -Property Name | Where-Object {$_.Count -gt 1 } | Select -ExpandProperty Group
   foreach ($field in $fields ) {
      foreach ($item in $field) {
         Write-Host "template " $sourceItem.Key $sourceItem.Value.FullName ": field " $item.Name " duplicated from templates " $item.ID $item.Template.FullName;  
      }
   }
}

It is useful to also know which data templates, that are not “folder templates”, don’t have fields and probably are not required

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) | where-object { $_.Value.FullName -like "User Defined*"}

$standardTemplate = Get-Item -Path "master:" -ID ([Sitecore.TemplateIDs]::StandardTemplate.ToString())
$standardTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$standardTemplate
$standardFields = $standardTemplateTemplateItem.OwnFields + $standardTemplateTemplateItem.Fields | Select-Object -ExpandProperty key -Unique

foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    $itemTemplateFields = $itemTemplateTemplateItem.OwnFields + $itemTemplateTemplateItem.Fields | Select-Object -ExpandProperty key -Unique
    
    $filterFields = $itemTemplateFields | Where-Object { $standardFields -notcontains $_ } | Sort-Object
    if ( $filterFields -eq $null){
       Write-Host "Template: " $sourceItem.Key $sourceItem.Value.FullName
    }  
}

Assigning icons to templates is always a good practice and reviewing this task is always part of the inspection as it gives the content authors context about the item the are manipulating.

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db)
foreach ($sourceItem in $templates) {
    if ([string]::IsNullOrEmpty($sourceItem["__Icon"])){
       Write-Host "Template: " $sourceItem.Key $sourceItem.Value.FullName
    }  
}

It is a good practice to use a token in the standard values of a template so it pre-populates values when a new item is created. For example, using $name for headline is a common practice.  

$db = Get-Database -Name "master"
#we can add | where-object { $_.Value.FullName -like "*Project*"} to filter a specific folder
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) 
foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    $itemTemplateStandard = $itemTemplateTemplateItem.StandardValues 
    if ($itemTemplateStandard -ne $null){
        $fields = $itemTemplateStandard.Fields
        ForEach ($field in $fields) {
            if ($field.Value -like "*$*"){
                Write-host $field.Value   $itemTemplateStandard.ID $itemTemplateStandard.FullName  
            }
        }
    }
}

We also need to review if the source fieldhas been set for Image fields which restricts where users can save images. At a minimum, image fields should have the source field set to /Sitecore/Media Library/Images, it would make content authoring more efficient to default the Image and file locations to specific root folders.

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db)

foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    $fields = $itemTemplateTemplateItem.Fields;
    
    ForEach ($field in $fields) {
        if (![string]::IsNullOrEmpty($field.Source)){
            Write-Host "TEMPLATE: " $sourceItem.Key $sourceItem.Value.FullName
            Write-host $field.Source         
        }
    }
}

More elegant way

$reportProps = @{
    Title = "Components with Image field"
    InfoTitle = "Title"
    InfoDescription = "Image fields without source: $($itemList.length)"
    PageSize = 50
}

$db = Get-Database -Name "master"
$templates= [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) 
$reportItems = @()

foreach ($sourceItem in $templates) {   
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate    
    $fields = $itemTemplateTemplateItem.Fields 
    ForEach ($field in $fields) {
        if ($field.Type -eq "Image"){
            if ([string]::IsNullOrEmpty($field.Source)){                
                $reportItem = [PSCustomObject]@{
                "Icon"=$Item.__Icon
                "Path"=$sourceItem.Value.FullName
                "Field"=$field.DisplayName
                "Source"=$field.Source
            }
            $reportItems += $reportItem 
            }    
        }
    }
}
$reportItems | Show-ListView @reportProps

If in the current Sitecore implementation, image source paths are not used we should see at least a Media Library well organized and that means that the authors are well trained.
Same should be reviewed for the Multilist, Treelist, Droplist, Droptree fields where we limit the content items being displayed on each.

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) 
foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    $fields = $itemTemplateTemplateItem.Fields
    ForEach ($field in $fields) {
        if ($field.Type -eq "Multilist" -Or $field.Type -eq "Multilist with Search" -Or $field.Type -eq "Treelist" -Or $field.Type -eq "TreelistEx" -Or $field.Type -eq "Droplink" -Or $field.Type -eq "Droptree"){
            if (![string]::IsNullOrEmpty($field.Source)){
                Write-Host "TEMPLATE: " $sourceItem.Key $sourceItem.Value.FullName
                Write-host $field.Source 
            }    
        }
    }
}

We could chage the logic if we want to see if any of these fields don’t have a source by applying if (![string]::IsNullOrEmpty($field.Source))

Source field is also used in rich text fields where it defines the editor profile used and it will determine the tools avaialble for edition. Too many or too few Editor tools may be available for the content authors and could generate not a clean markup if we allow them to add anything.

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) 

foreach ($sourceItem in $templates) {   
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate    
    $fields = $sourceItem.Value.GetFields($false)
    
    ForEach ($field in $fields) {        
        if ($field.Type -eq "Rich Text"){
            if (![string]::IsNullOrEmpty($field.Source)){
                Write-Host "TEMPLATE: " $sourceItem.Key $sourceItem.Value.FullName
                Write-host $field.Source 
            }    
        }        
    }
}

Evaluate all data templates and consider setting rich text editor profiles for all of them to limit functionality of the editor to suit the desired field function.

If we want to also inpect if templates have workflow set, we can run a simple script that will tell us which ones have workflows

$reportProps = @{
    Title = "Templates with Workflow"
    InfoTitle = "Workflow"
    PageSize = 50
}
$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) | where-object { $_.Value.FullName -like "Feature*"}
$reportItems = @()

foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    
    $itemTemplateStandard = $itemTemplateTemplateItem.StandardValues 
    
    if ($itemTemplateStandard -ne $null){
        $standardItem = Get-Rendering -Item $itemTemplateStandard
        
        if (![string]::IsNullOrEmpty($itemTemplateStandard["__Default workflow"])){  
            $reportItem = [PSCustomObject]@{
              "Icon"=$itemTemplateStandard.__Icon
              "Path"=$itemTemplateStandard.Paths.Path
              "Source"=$itemTemplateStandard["__Default workflow"]
            }
            $reportItems += $reportItem 
        }
    } 
}
$reportItems | Show-ListView @reportProps

Sometimes, you might want to look for pages that don’t have presentation details.

$db = Get-Database -Name "master"
$templates = [Sitecore.Data.Managers.TemplateManager]::GetTemplates($db) | where-object { $_.Value.FullName -like "Project*"}

foreach ($sourceItem in $templates) {
    $itemTemplate = Get-Item -Path "master:" -ID $sourceItem.Key
    $itemTemplateTemplateItem = [Sitecore.Data.Items.TemplateItem]$itemTemplate
    
    $itemTemplateStandard = $itemTemplateTemplateItem.StandardValues 
    
    if ($itemTemplateStandard -ne $null){
        $standardItem = Get-Rendering -Item $itemTemplateStandard
            
        if ($standardItem -eq $null ){
            write-host "This item doesn't have renderings " $itemTemplateStandard.ID       
        }
    } 
    else{
        write-host "This item doesn't have a Standard Value item " $itemTemplateTemplateItem.ID    
    }
}

I will keep adding more scripts related to templates if needed.

In the next series, I will provide more guides how to inspect content, layouts, and media and code.

Happy scripting 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s