Content hub DAM connector and xmcloud

Content hub DAM connector and xmcloud

In this article, I will share some recent changes in the connector for DAM and look at a few useful extensions for finding broken links and updating assets.

Setup Connector

Setting up the connector is well documented on the Sitecore documentation and I will not spend much time on this topic, you can check the following:

After configuring the connector correctly, you may need to configure the mappings.

The Mappings can be found under /sitecore/system/Modules/DAM/Config/Mapping and are available for:

  • File Field
  • Image Field
  • General Link Field

Basically, you can map the properties of the assets to the custom attributes that will be saved on the field.

To see the available properties, you can check the content hub API:
https://[your contenthub domain]/api/entities/[entity id]

When you know the correct property name you can create a mapping for it in Sitecore, for example:

💡
Gotcha: I have made this mistake quite a few times, on the General Link Field Mapping, the first field is the Content Hub Attribute
and the second field is Sitecore Attribute, but for the Image field mapping the field order is reversed.

When the author adds a link from the content hub Sitecore will use these mappings to store the information on the field, you can see this information on the raw value of the field, here is an example for a general link:

The downside is that if these pieces of information are changed on the asset in the content hub, the Sitecore fields will be outdated.

To address that Sitecore has created two dam functions in the content editor:

Currently, Sync is available for the General Link and Image fields.

Recent Changes

Sitecore has added two new mappings, which are replacements for the older attributes:

  • dam-id: replacement for stylelabs-content-id
  • dam-content-type: replacement for stylelabs-content-type

now if you add a new link or image from the content hub it will use the new attributes. If you use the sync function on the older images or link fields it will not remove the old attributes and replace them with the new one.

💡
If your code (backend or frontend) has dependencies on the stylelabs-content-id or stylelabs-content-type you need to adjust it to support both attributes.

Because the Sitecore DAM is a one-way sync that reads the asset information and stores it on the field, if the asset is removed, it would be quite challenging to find the broken links of the assets from the content hub.

We can simply use Powershell:

function Test-GeneralLinkField {
    param (
        $field
    )

    [Sitecore.Data.Fields.LinkField]$field = $field
    $url = $field.getattribute("url")
    try {
        Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Ignore | Out-Null
        return $true
    }
    catch {
        # Add Logging if ncecessary
    }
    
    return $false
}

function Test-ImageField {
    param (
        $field
    )

    [Sitecore.Data.Fields.ImageField]$field = $field
    $url = $field.getattribute("src")
    try {
        Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Ignore | Out-Null
        return $true
    }
    catch {
        # Add Logging if ncecessary
    }
    
    return $false
}

And the usage is:

$item = get-item -path "master:" -id "{AE35A1A6-0569-488E-80E8-9353B708BDCF}" -language "en"

Test-GeneralLinkField  -field $item.Fields["downloadlink"]

$item = get-item -path "master:" -id "{6EC98578-4580-42C7-9DD3-1C49BC8A98AE}" -language "en"

Test-ImageField  -field $item.Fields["image"]

Updating alt attribute for image field

As mentioned before the Sitecore has Sync for General Link and Image field. If you are in a situation where the Sitecore Sync is not enough then you can check this example.

as a first step you need to get access token, which is quite easy when you use xmcloud:

# Return Access Token for Contenthub based on the connection string in xmcloud
function Get-CHAccessToken {
    $connection = $env:Sitecore_ConnectionStrings_DAM_dot_ContentHub.split(';')
    if (-not $connection) {
        throw "Contenthub connection string is not configured."
    }
    $clientID = ($connection | Where-Object { $_.contains('ClientId=') } | Select-Object -First 1).split('=')[1]
    $clientSecret = ($connection | Where-Object { $_.contains('ClientSecret=') } | Select-Object -First 1).split('=')[1]
    $userName = ($connection | Where-Object { $_.contains('UserName=') } | Select-Object -First 1).split('=')[1]
    $password = ($connection | Where-Object { $_.contains('Password=') } | Select-Object -First 1).split('=')[1]
    $uri = ($connection | Where-Object { $_.contains('URI=') } | Select-Object -First 1).split('=')[1]

    $body = @{
        grant_type = 'password'
        client_id        = $clientID
        client_secret    = $clientSecret
        username         = $userName
        password         = $password
    }
    $contentType = 'application/x-www-form-urlencoded' 
    $accessTokenRequest = Invoke-WebRequest -Method Post -Uri "$uri/oauth/token" -body $body -ContentType $contentType -UseBasicParsing
    $token = ($accessTokenRequest.Content | ConvertFrom-Json).access_token

    return $token
}

After having the access token you need to adjust the code below based on field mappings in Sitecore and in content hub, it will support both style-labs and dam-id attributes:

# the function is in the Get-CHAccessToken.ps1
Import-Function Get-CHAccessToken

function Get-CHDamUri {
    $connection = $env:Sitecore_ConnectionStrings_DAM_dot_ContentHub.split(';')
    if (-not $connection) {
        throw "Contenthub connection string is not configured."
    }
    return ($connection | Where-Object { $_.contains('URI=') } | Select-Object -First 1).split('=')[1]
}

function Get-CHAltByEntityId {
    param (
        $entityId,
        $accessToken
    )

    $authHeaders = @{
        'Content-Type'  = 'application\json'
        'Authorization' = "Bearer $accessToken"
    }
    
    $URL = "$(Get-CHDamUri)/api/entities/$entityId/"
    try {
        $depResponse = Invoke-WebRequest -Headers $authHeaders -Uri $URL -UseBasicParsing -ErrorAction Ignore
        $contents = ($depResponse.Content | ConvertFrom-Json)
        
        return $contents.properties.'[YOUR ALT ATTRIBUTE ON Contenthub]'
    }
    catch {
        # Add Logging if ncecessary
        # In the case the image does not exists and you will get 404
    }

    return $null
}

function Get-CHAltByDamId {
    param (
        $damId,
        $accessToken
    )

    $authHeaders = @{
        'Content-Type'  = 'application\json'
        'Authorization' = "Bearer $accessToken"
    }
    
    $URL = "$(Get-CHDamUri)/api/entities/query?query=identifier=='$damId'"
    try {
        $depResponse = Invoke-WebRequest -Headers $authHeaders -Uri $URL -UseBasicParsing -ErrorAction Ignore
        $contents = ($depResponse.Content | ConvertFrom-Json)
        if ($contents.items.Length) {
            return $contents.items[0].properties.'[YOUR ALT ATTRIBUTE ON Contenthub]'
        }
    }
    catch {
        # Add Logging if ncecessary
        # In the case the image does not exists and you will get 404
    }
    
    return $null
}

function Update-CHImageFieldAlt {
    param (
        $imageField,
        $accessToken
    )
    [Sitecore.Data.Fields.ImageField]$field = $imageField
    # reading alt attribute
    $sId = $field.GetAttribute("stylelabs-content-id")
    $damId = $field.GetAttribute("dam-id")        
    $alt = $field.GetAttribute("alt")
    if (-not $sId -and -not $damId) {
        # Log Error the item is invalid
        continue
    }
    
    $newAlt = $null
    if ($damId) {
        $newAlt = Get-CHAltByDamId -damId $damId -accessToken $accessToken
    }
    elseif ($sid) {
        $newAlt = Get-CHAltByEntityId -entityId $sId -accessToken $accessToken
    }

    if (-not $newAlt) {
        # Log Error if necessary
        continue
    }

    if ($newAlt -eq $alt) {
        # No change is necessary
        continue
    }
    $imageField.Item.Editing.BeginEdit() | Out-Null
    $field.SetAttribute("alt", $newAlt)
    $imageField.Item.Editing.EndEdit() | Out-Null
}

Usage example:

$item = get-item -path "master:" -id "{65606EA5-B5A3-4835-B6C4-D9754C8AE6F4}" -language "en"
$accessToken = Get-CHAccessToken

Update-CHImageFieldAlt  -imageField $item.Fields["image"] -accessToken $accessToken

Keep in mind, the code needs changes and this is just a simple example.



Last Words

Sitecore xmcloud has good support and integration for the content hub. If you want to use any of these functions, I suggest to use some filtering to prevent performance penalty and make sure you do not use Get-AccessToken function in a loop.

For example, filter out irrelevant templates, using a function:

function HasValidTemplate {
    param (
        [guid]$itemId
    )

    # List of excluded template IDs
    $excludes = @(
        [guid]::Parse("{6F108E3C-5D57-42F8-A910-C22920269AAA}"),
        [guid]::Parse("{6F108E3C-5D57-42F8-A910-C22920269BBB}"),
        # All irrelevant templates

    )

    # Check if the item ID is in the list of excluded template IDs
    if ($excludes -contains $itemId) {
        return $false
    }

    return $true
}

$items = Get-ChildItem -Path "master:" -ID "{AAAABBFE-F076-4989-A4D3-D0416B2EB353}" -language "en" -Recurse -WithParent | where { (HasValidTemplate -itemid $_.templateid.guid) }

Source code on the Github.