Virtual Happy Hour this month, Jun 28, we'll be getting a sneak preview at our soon to launch SaaS CMS!

Try our conversational search powered by Generative AI!

Get references to references that is referencing content

Vote:
 

Hello there! I am trying to make an image sitemap for our website.  We are using CMS 10.

So I've gotten the all our images and I can get the references for them using IContentRepository.GetReferencesToContent(), and I've filtered through so that I've gotten the once that are being used somewhere and are published. So that all works.

However, Images that are used in a block will always get through the filter since they are always used, and a block has to be published in order to exist.
But the block itself may not be used on a published page, and in that case I would not want to index the Image that is in the unused block to the sitemap.. 

I could just run another "GetReferencesToContent" on the referencing block too, but I can't know how many levels I have to go in order to cover all the references.

One image may be inside a block that is referenced by another block that referenced by a page like:
Image > Block > Block  > Page

And another may be inside a Bock that is just referenced by a page like
Image > Block > Page

It may be even more levels also, and I dont want to build a freakin' pyramid of "GetReferencesToContent" in order to cover all possible levels, obviously.
It would be awesome if there were a function like GetAncestors() but for references and not parents, or something similar.

Does anyone have a clever idea as to how to tackle this issue? 

private IList<ImageFile> GetImages()
        {
            var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
            var assetsRoot = SiteDefinition.Current.GlobalAssetsRoot;
            var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
            var allImages= repo.GetDescendents(assetsRoot).Where(x => repo.Get<IContent>(x) is ImageFile).Select(x => x.ToReferenceWithoutVersion()).Distinct().Select(repo.Get<ImageFile>).ToList();

            var imagesInUse = new List<ImageFile>();
            foreach (ImageFile image in allImages)
            {
                var referencingContentLinks = repository.GetReferencesToContent(image.ContentLink, false).Where(x => IsContentPublished(x.OwnerID.GetContent(true, null)));

                if (referencingContentLinks.Any())
                {
                    imagesInUse.Add(image);
                }
            }
            return imagesInUse;
        }
#296053
Feb 06, 2023 15:33
Vote:
 

Not specific to images be we have a developer dashboard we use to help with housekeeping where we try to solve this problem. It focuses on using IContentModelUsage.

The code sample below is an extract of some extension methods we use - I'm not going to say it is as pretty or as well written as it could be, as it is mostly a dev tool, but hopefully it provides some guidance,

        public static IContentModelUsage ContentModelUsage()
        {
            return ServiceLocator.Current.GetInstance<IContentModelUsage>();
        }

        public static IContentSoftLinkRepository ContentSoftLinkRepository()
        {
            return ServiceLocator.Current.GetInstance<IContentSoftLinkRepository>();
        }

        public static bool IsUsed(this ContentType contentType)
        {
            var usages = ContentModelUsage().ListContentOfContentType(contentType)
                .Select(usage => usage.ContentLink.ToReferenceWithoutVersion())
                .Distinct();

            foreach (var usage in usages)
            {
                if (usage.IsUsed())
                    return true;
            }

            return false;
        }

        public static bool IsUsed(this ContentReference contentLink)
        {
            if (contentLink.IsPage())
                return true;

            var links = contentLink.GetSoftLinks();
            foreach (var link in links)
            {
                if (link.OwnerContentLink.IsUsed())
                    return true;
            }

            return false;
        }

        public static IEnumerable<SoftLink> GetSoftLinks(this ContentReference contentLink)
        {
            return ContentSoftLinkRepository().Load(contentLink, true)
                .Where(link => !ContentReference.IsNullOrEmpty(link.OwnerContentLink));
        }

        public static bool IsPage(this ContentReference contentLink)
        {
            var content = contentLink.GetContent<IContent>();
            return content.IsPage();
        }

        public static bool IsPage(this IContent content)
        {
            return content.GetOriginalType().IsSubclassOf(typeof(PageData));
        }

This is then set in our view model, of which I've included relevant parts below:

                var usages = ContentModelUsage.Service.ListContentOfContentType(contentType);

                var items = usages
                    .Select(usage => usage.ContentLink.ToReferenceWithoutVersion())
                    .Distinct()
                    .Select(contentReference =>
                        ContentRepository.Service.Get<IContent>(contentReference)).ToList();

                var modelContentType = new ContentTypeViewModel
                {
                    ID = contentType.ID,
                    DisplayName = contentType.DisplayName,
                    FullName = contentType.FullName,
                    ModelType = contentType.ModelType,
                    Base = contentType.Base.ToString(),
                    IsUsed = contentType.IsUsed()
                };

One day I might rewrite this to be a bit more pretty, in fact one day I'll probably have to when we get around to looking at CMS 12. 

#299207
Edited, Mar 29, 2023 13:50
Vote:
 

Actually I think I may have gone too specific to our use case in some of that and it is probably more IContentSoftLinkRepository that you need. Our dev tools looks at if a content type is used as a whole, we then drill into individual usages. On our dev tool it actually only allows the option to delete blocks if it isn't used on a page, which then in turn allowed us to see if we could remove the content type entirely as a means to clean up a legacy project. But you should be able to use the same approach to see which images are used.

They key to that is the .IsUsed() extension method targeting the content reference.

#299208
Edited, Mar 29, 2023 14:03
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.