This Craft CMS plugin adds a Field Type with GraphQL support for the Canto Digital Asset Management (DAM) web system.
This plugin works by leveraging a modified version of the Canto Universal Connector to provide the asset picker for your Canto library inside of Craft CMS.
It stores all of the selected Canto asset data in Craft CMS as a JSON blob, which can be accessed via Twig or GraphQL.
The Canto asset data is represented as a Laravel Collection, allowing you to use the various Collection methods to search, sort, filter, etc. the data.
This plugin requires Craft CMS 4.4.0 or later, and PHP 8.0.2 or later.
Before you can use the Canto DAM Assets plugin, you need to configure it via Settings → Plugins → Canto DAM Assets:
- App ID - Your Canto DAM Application ID
- Secret Key - Your Canto DAM secret key
- Authentication Endpoint - The URL that should be used to authenticate and obtain an access token from. You can include the
{appId}
&{secretKey}
tokens in the URL, which will be replaced with the respective values from the settings - Tenant Host Name - The hostname for the Canto Tenant, e.g.
rubin.canto.com
- Webhook Secure Token - The secure token that must match the token coming from the Canto webhook Secure Token field to allow changes to be synced from Canto
Each Canto DAM Assets field can be configured via its Field Settings, to control what Canto assets can be chosen by the field:
- Single Image - Only a single image can be picked for use in the field
- Multiple Images - Either a single image, or multiple images (via the round checkbox in the upper-right corner of the image) can be selected
- Entire Album - Only an entire album of images can be selected
You can have multiple Canto DAM Asset fields for any given entry, each with their own separate Field Settings.
Since the Canto Asset Picker Type is just a field setting, admins can at any time change what the content author is allowed to pick. Changing this setting for an existing field does not change the assets stored in the field, it just alters what UX is presented to the content author in the future.
You can have multiple Canto DAM Asset fields in any entry in Craft CMS:
If a single image is selected, a preview of it will appear with the name of the image, and the album it comes from.
If you have multiple images selected (such as for an entire album of images), a preview of the first image will appear with the number of images and the album they come from, with a stack below it as a visual cue that there are multiple images.
Clicking on Remove Images will remove the images from the field.
Clicking on Choose a Different DAM Asset will bring up the Canto Universal Connector UX the allows you to choose the asset(s) to use:
To choose a single asset, click on the image for a detail view, when click on Confirm to use the image:
To select multiple assets, click on the circle in the upper-right corner of each image (a âś“ will appear), and then click on the Insert button:
To select an entire album, click on the album in the list view on the left, then click on the Insert Album button:
Just like Assets in Craft CMS, each Canto DAM Field stores data for an array of n
assets. So a single Canto DAM Asset is treated the same as a gallery of 10 Canto DAM Assets.
The data the Canto DAM Asset field type stores is as follows, each in their own separate table in the content
table of the Craft CMS database. The separate content
table columns allows for easy searching based on the cantoId
or cantoAlbumId
which are broken out of their respective data structures:
cantoId
- Theid
of the Canto Asset, or0
if this is a collection of imagescantoAlbumId
- Theid
of the Canto AlbumcantoAssetData
- A JSON blob that is an array of Canto DAM AssetscantoAlbumData
- A JSON blob of Canto DAM Album data
The selected Canto DAM Asset data is stored as a JSON blob with a structure that mirrors the results from the batch/content
endpoint combined with the data from the undocumented batch/directuri
endpoint.
Below are the available fields for the Canto Asset Data of the scheme image
; the fields may vary for other asset types:
{
id
uid
metadata {
BitsPerPixel
FileTypeDetail
FileTypeExtension
FlightFileExtension
GIFVersion
BackgroundColor
AnimationIterations
WHRotated
RSize
FlightFileType
FileName
ColorResolutionDepth
Comment
CreateDate
FileInodeChangeDateTime
FileType
ImageSize
FileAccessDateTime
ImageHeight
Orientation
ImageWidth
DurationTime
FileModificationDateTime
MIMEType
FinfotoolVersion
AssetDataSizeLong
Megapixels
HasColorMap
FrameCount
Panoramas
}
height
relatedAlbums {
id
}
md5
approvalStatus
ownerName
smartTags
dpi
lastUploaded
versionHistory {
no
ownerName
created
time
version
comment
currentVersion
}
created
keyword
time
tag
additional {
Description
UploadedBy
WebDAMGroupID
SpatialReferenceValue
SpatialCoordinateSystemProjection
MetadataVersion
UploaderContact
Credit
WebDAMPublisherID
AltTextES
PublisherID
SpatialReferenceDimension
WebDAMPublisher
AltTextEN
ID
SocialMediaDescription
TitleES
TitleEN
MediaConsent
SpatialRotation
Title
Publisher
SpatialScale
SpatialReferencePixel
WebDAMSublocation
SpatialCoordinateFrame
CaptionES
Type
SocialMediaHandles
CaptionEN
UsageTerms
WebDAMMediaType
}
url {
preview
download
metadata
HighJPG
PNG
directUrlOriginal
detail
directUrlPreview
directUrlPreviewPlay
LowJPG
}
width
name
default {
Size
UploadedBy
Dimensions
GPS
DateUploaded
DateModified
Name
Copyright
ModifiedBy
LowJPG
ContentType
Author
DateCreated
Resolution
}
size
scheme
owner
}
You can use the GraphiQL IDE built into Craft CMS to explore the data structure interactively.
You can access the data stored in a Canto DAM Asset field type via Twig by accessing it as you would any other field type. In the examples below, someDamAsset
is the field handle of a Canto DAM Asset field:
{{ entry.someDamAsset.cantoAssetData.first().url.directUrlOriginal }}
Note that cantoAssetData
is a Laravel Collection that contains an array
of Canto DAM Assets, so we are using the .first()
method to get the first item from the array, and then we are accessing the url.directUrlOriginal
of the Canto DAM Asset data structure.
Since cantoAssetData
is a Laravel Collection, all Collection methods are available to operate on the Canto DAM Asset data.
Craft CMS also provides a .one()
method that aliases to .first()
, so you can use that as well to mirror how Element Queries work, if you like.
You can also access the data stored in a Canto DAM Asset field type via Craft CMS's GraphQL API inside of an Entry query.
In the examples below, someDamAsset
is the field handle of a Canto DAM Asset field:
{
entry(section: "homepage") {
... on homepage_homepage_Entry {
someDamAsset {
id,
url {
directUrlOriginal
}
}
}
}
}
For a Canto DAM Assets field that has one asset, the response will look like this:
{
"data": {
"entry": {
"someDamAsset": [
{
"id": "vjbnb7df8t6b3al3sumpo66s0n",
"url": {
"directUrlOriginal": "https://Example.canto.com/direct/image/vjbnb7df8t6b3al3sumpo66s0n/ZgZdSVOt9ZxsSjf1AKd4ficjBcE/original?content-type=image%2Fjpeg&name=IMG_0599.jpeg"
}
}
]
}
}
}
For a Canto DAM Asset field that contains multiple assets, the response will look like this:
{
"data": {
"entry": {
"someDamAsset": [
{
"id": "vjbnb7df8t6b3al3sumpo66s0n",
"url": {
"directUrlOriginal": "https://Example.canto.com/direct/image/vjbnb7df8t6b3al3sumpo66s0n/ZgZdSVOt9ZxsSjf1AKd4ficjBcE/original?content-type=image%2Fjpeg&name=IMG_0599.jpeg"
}
},
{
"id": "docn3e2imd5cncpfk78aatvb1h",
"url": {
"directUrlOriginal": "https://Example.canto.com/direct/image/docn3e2imd5cncpfk78aatvb1h/m8MCieLzJVnwfofy5HG9ZSi6qF8/original?content-type=image%2Fjpeg&name=IMG_0790.jpeg"
}
},
{
"id": "k9qquhbn2l2vn4c8t9jnr5l90e",
"url": {
"directUrlOriginal": "https://Example.canto.com/direct/image/k9qquhbn2l2vn4c8t9jnr5l90e/afyd7vgCFoqesmf6JrEejLwHaIM/original?content-type=image%2Fjpeg&name=IMG_0604.jpeg"
}
},
{
"id": "v7jrfok1r57k5fq20trm8bnt0a",
"url": {
"directUrlOriginal": "https://Example.canto.com/direct/image/v7jrfok1r57k5fq20trm8bnt0a/RqGiM6RPSnndSIMlbCy5YeOt4BQ/original?content-type=image%2Fjpeg&name=IMG_0602.jpeg"
}
}
]
}
}
}
In both cases, the data will be a JSON array of Canto DAM Asset data structures.
You can also treat the Canto DAM Asset data as a database, and do queries based on the content of fields or sub-fields there.
This works by mapping a subset of Collection methods to arguments in GraphQL.
In the examples below, someDamAsset
is the field handle of a Canto DAM Asset field.
So for example, this query:
{
entry(section: "homepage") {
... on homepage_homepage_Entry {
someDamAsset(where: {key: "default.Author", value: "Hernan Stockebrand"}, sortByDesc: { field: "default.Size", flags: SORT_NUMERIC }) {
id,
url {
directUrlOriginal
}
}
}
}
}
...will return all of the Canto DAM Assets in the someDamAsset
field that have the default.Author
set to Hernan Stockebrand
, sorted in descending order by the default.Size
field.
Only a subset of Collection methods are available as arguments in your GraphQL query, because only some Collection methods return Collection data directly.
Here's a list of the available arguments, and the types they expect as parameters:
-
except
:[Int]
- Get all items except for those with the specified indexes. -
first
:Boolean
- Get the first item from the collection. -
last
:Boolean
- Get the last item from the collection. -
nth
:Int
- Return a collection consisting of every n-th element. -
random
:Int
- Get the specified number of items randomly from the collection. -
reverse
:Boolean
- Reverse the list -
shuffle
:Int
- Shuffle the items in the collection, using the value as a random number seed. -
skip
:Int
- Skip the first N items. -
sortBy
:SortByInput
- Sort the collection using the sort string(s). You can use thefield.subField
syntax for nested fields and provide multiple sort commands as a list of strings. -
sortByDesc
:SortByDescInput
- Sort the collection using the sort string(s) in a descending order. You can use the field.subField syntax for nested fields and provide multiple sort commands as a list of strings. -
forPage
:ForPageInput
- Paginate the items by page number and items per page. -
where
:[WhereFiltersInput]
- Get all items by the given key value pair, using the optional operator for comparison. -
whereBetween
:[WhereBetweenFiltersInput]
Filter items such that the value of the given key is between the given values. -
whereIn
:[WhereInFiltersInput]
- Filter items such that the value of the given key is in the array of values provided. -
whereNotBetween
:[WhereNotBetweenFiltersInput]
- Filter items such that the value of the given key is NOT between the given values. This argument expects exactly three values in an array. You can use the field.subField syntax for nested fields. -
whereNotIn
:[WhereNotInFiltersInput]
- Filter items by the given key value pair, making sure the value is not in the array. -
whereNotNull
:String
- Return items from the collection where the given key is not null. You can use thefield.subField
syntax for nested fields. -
whereNull
:String
- Return items from the collection where the given key is null. You can use thefield.subField
syntax for nested fields.
-
ForPageInput
- Used with theforPage
argument, in the format:{page: 1, items: 10}
:-
page
:Int
- The page number -
items
:Int
- The number of items per page
-
-
SortByInput
- Used with thesortBy
argument, in the format:{field: "field", flags: SORT_REGULAR}
:-
field
:[String]
- The field(s) to sort by -
flags
:String
- PHP sort flags that determine how items are compared. Defaults toSORT_NATURAL_CASE
-
-
SortByDescInput
- Used with thesortByDesc
argument, in the format:{field: "field", flags: SORT_REGULAR}
:-
field
:[String]
- The field(s) to sort by -
flags
:String
- PHP sort flags that determine how items are compared. Defaults toSORT_NATURAL_CASE
-
-
WhereFiltersInput
- Used with thewhere
argument, in the format:{key: "key", value: "value", operator: "operator"}
:-
key
:String
- The key to search on, you can use thefield.subField
syntax for nested fields -
value
:String
- The value to match when searching -
operator
:String
- The comparison operator to use, e.g.: =, >, <=, etc. The default is =
-
-
WhereBetweenFiltersInput
- Used with thewhereBetween
argument, in the format:{key: "key", values: ["value1", "value2"]}
:-
key
:String
- The key to search on, you can use thefield.subField
syntax for nested fields -
values
:String
- The values that the key should be between
-
-
WhereInFiltersInput
- Used with thewhereIn
argument, in the format:{key: "key", values: ["value1", "value2"]}
:-
key
:String
- The key to search on, you can use thefield.subField
syntax for nested fields -
values
:String
- The values that should be in the key
-
-
WhereNotBetweenFiltersInput
- Used with thewhereNotBetween
argument, in the format:{key: "key", values: ["value1", "value2"]}
:-
key
:String
- The key to search on, you can use thefield.subField
syntax for nested fields -
values
:String
- The values that the key should not be between
-
-
WhereNotInFiltersInput
- Used with thewhereNotIn
argument, in the format:{key: "key", values: ["value1", "value2"]}
:-
key
:String
- The key to search on, you can use thefield.subField
syntax for nested fields -
values
:String
- The values that should not be in the key
-
You can use the GraphiQL IDE built into Craft CMS to try queries interactively.
The Canto DAM Assets plugin has several controller API endpoints, to allow for the syncing of data from Canto webhooks to Craft:
_canto-dam-assets/sync/delete-by-canto-id
- This will delete a Canto Asset from any Canto DAM Assets fields. Example:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"album":"N29BC", "displayname":"ADASS Poster 6-23.png", "id":"tsfhf1snqh5533j96gi2ntom7j", "scheme":"image", "secure_token":"abc"}' \
http://plugindev.local:8004/actions/_canto-dam-assets/sync/delete-by-canto-id
_canto-dam-assets/sync/delete-by-album-id
- This will delete an entire Canto Album from any Canto DAM Assets fields. Example:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"album":"N29BC", "displayname":"ADASS Poster 6-23.png", "id":"psnne2p0717un0d115ftjd4l1a", "scheme":"image", "secure_token":"abc"}' \
http://plugindev.local:8004/actions/_canto-dam-assets/sync/delete-by-album-id
_canto-dam-assets/sync/update-by-canto-id
- This will update the metadata for a Canto Asset in any Canto DAM Assets fields. Example:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"album":"N29BC", "displayname":"ADASS Poster 6-23.png", "id":"psnne2p0717un0d115ftjd4l1a", "scheme":"image", "secure_token":"abc"}' \
http://plugindev.local:8004/actions/_canto-dam-assets/sync/update-by-canto-id
_canto-dam-assets/sync/update-by-album-id
- This will update and entire Canto Album in any Canto DAM Assets fields. Example:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"album":"N29BC", "displayname":"ADASS Poster 6-23.png", "id":"psnne2p0717un0d115ftjd4l1a", "scheme":"image", "secure_token":"abc"}' \
http://plugindev.local:8004/actions/_canto-dam-assets/sync/update-by-album-id
The secure_token
setting in each Canto webhook needs to match the Webhook Secure Token plugin setting for it to be considered valid.
Some things to do, and ideas for potential features:
- Add preview support for the display of video, PDFs, etc. in the Canto DAM Asset field type
- Convert the
canto-field.js
to TypeScript - Add tests to the plugin
- Consider submitting the plugin to the Plugin Store
- Consider submitting the plugin to the Canto Integrations page
Brought to you by nystudio107