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 😉