1

I'm trying to see if I can over-ride the datasource location of an image field for content editors when selecting the data source (not at render time). E.g in the browse media dialog window that is displayed. This needs to work for both Content Editor and Experience Editor.

It doesn't look to be possible and involves instead creating a custom copy of the image field instead:

How to change the source from an image field programmatically? https://stackoverflow.com/questions/24540419/sitecore-programmatically-change-imagefield-datasource Sitecore Image Field Source to filter on template type

It seems like quite a bit of work just to do this. I wondered if there is a pipeline I can hook into instead to achieve this?

I'm using Sitecore 8.2 update 7.

Update

Attempt 1 - I tried using setting the field datasource to: code:MyCustom.CodeSource, MyCustomAssembly with code similar to Pete's answer here: Override the source of a template field.

internal class CustomCodeSource
{
    [UsedImplicitly]
    public void Process(GetLookupSourceValueListsArgs args)
    {
        Assert.ArgumentNotNull(args, "args");

        if (!args.Source.StartsWith("code:")) return;

        var values = RunEnumeration(args.Source);
        if (values != null && values.Length > 0)
            args.Result.AddRange(values);
        args.AbortPipeline();
    }

    private static string[] RunEnumeration(string templateSource)
    {
        templateSource = templateSource.Replace("code:", string.Empty);
        var valueList = ReflectionUtility.CreateInstance(Type.GetType(templateSource)) as IValueList;
        return valueList == null ? new string[0] : valueList.ValueListQuery();
    }

}

This doesn't fire for Image fields for some reason but does for other types such as Multilist fields.

Attempt 2 - As it was mentioned in the Comments that SXA support this too (https://doc.sitecore.com/developers/sxa/19/sitecore-experience-accelerator/en/use-a-prefix-to-set-the-data-source-context.html) I've tried adding similar code to the SXA CodeDatasource for both the getLookupSourceItems and resolveRenderingDatasource pipeline as I wondered if SXA's pipelines had something magic in them (that maybe supports image fields) given that it overrides the CustomDataSource from the Sitecore.Buckets dll like so:

<getLookupSourceItems>
        <processor patch:instead="*[@type='Sitecore.Buckets.FieldTypes.CustomDataSource, Sitecore.Buckets']"
                   type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.GetLookupSourceItems.CodeDatasource, Sitecore.XA.Foundation.LocalDatasources" resolve="true" />
    </getLookupSourceItems>

Here is my code:

   public class GetCustomLookupSourceItems
    {
        public CodeDatasourceService CodeDatasourceService { get; set; }

        public GetCustomLookupSourceItems()
        {
            this.CodeDatasourceService = new CodeDatasourceService();
        }
        public void Process(GetLookupSourceItemsArgs args)
        {
            Assert.IsNotNull((object)args, nameof(args));
            if (!args.Source.StartsWith("code:", StringComparison.OrdinalIgnoreCase))
                return;
            foreach (string oldValue in ((IEnumerable<string>)args.Source.Split('|')).Where<string>(
                (Func<string, bool>)(x => x.StartsWith("code:", StringComparison.OrdinalIgnoreCase))))
            {
                args.Source = args.Source.Replace(oldValue, string.Empty).TrimStart('|').TrimEnd('|');
                string typeName = oldValue.Replace("code:", string.Empty);
                if (args.Item != null)
                {
                    Item[] datasoureces = CodeDatasourceService.GetDataSources(args.Item, typeName);
                    if (((IEnumerable<Item>)datasoureces).Any<Item>())
                        args.Result.AddRange(datasoureces);
                }
            }

            if (args.Source.Length != 0)
                return;
            ((PipelineArgs)args).AbortPipeline();
        }
    }

It's patched in like so:

  <getLookupSourceItems>
    <processor
        patch:instead="*[@type='Sitecore.Buckets.FieldTypes.CustomDataSource, Sitecore.Buckets']"
        type="MyCustom.GetCustomLookupSourceItems, MyCustomAssembly">
    </processor>
 </getLookupSourceItems>

(CodeDatasourceService is pretty much like the SXA one but is concrete for now)

I also added similar code to the SXA ResolveRenderingDatasource.CodeDatasource and patched it in like so:

 <resolveRenderingDatasource>
                <processor type="MyCustom.ResolveCustomRenderingDatasource, MyCustomAssembly" resolve="true" />   
</resolveRenderingDatasource>

Lastly I then tried setting the image field datasource to: code:MyCustom.CodeSource, MyCustomAssembly hoping the SXA approach would mean it would now work for image fields. Again this doesn't work, it is never called for image fields for some reason.

Has anyone done this successfully in another way?

I'm stumped and it feels like I'm going to have to go with replacing the field which seems pretty horrible and I'd really rather not do this: https://sitecoreoverload.blogspot.com/2018/09/adding-dynamic-image-source-for-multi.html.

1
1

I got asked recently If I ever solved this in an elegant way. The simple answer is unfortunately not. I had to duplicate the image field and add in my service to resolve the paths and also over-ride the Choose image dialog in experience editor (similar to this post: https://sitecoreoverload.blogspot.com/2018/09/adding-dynamic-image-source-for-multi.html). Below are the changes I made and code I ended up with for 8.2 u7 but the code may differ slightly for 9.x and above so please double check it against your version:

MultisiteImage.cs

/// <summary>
/// Copy of out of the box image field to support resolving Multisite Image paths
/// this is now used for all components with images as it needs to be referenced instead of the out of the box one in order to achieve this
/// </summary>
public class MultiSiteImage : Image
{
    public IMultisiteService MultisiteService { get; set; }
    
    public MultiSiteImage()
    {
           IMultisiteService = LocatorContainerManager.Current.Resolve<IIMultisiteService>(); 
    }

    protected new void BrowseImage(ClientPipelineArgs args)
    {
        Assert.ArgumentNotNull((object) args, nameof (args));
      
        if (!args.IsPostBack)
        {
            //default image path
            string path = StringUtil.GetString(this.Source, "/sitecore/media library");

            //resolve Image Field DataSource Path here with some kind of service
            var imagePath = IMultisiteService.GetImageFieldDatasourcePath(path, args, this);

            if(!string.IsNullOrEmpty(imagePath)){
                Source = imagePath;
            }
        }
        //check the path exists
        if (!string.IsNullOrEmpty(Source)){
            Item sourceItem = Client.ContentDatabase.GetItem(Source);
            if(sourceItem == null){
                throw new ClientAlertException($"The source of this Image field points to an item that does not exist: {Source}."); 
            }
        }
        base.BrowseImage(args);
    }

}

note: please replace the Multisite Service and methods with your own.

Core DB Update

Ensure you update the out of the box image field in the Core database to use the new class above instead of the out of the box image field: /sitecore/system/Field types/Simple Types/Image

MultisiteChooseImage.cs

/// <summary>
/// override out of the box image browser in experience editor to support resolving Multisites Image paths
/// </summary>
class MultisiteChooseImage : ChooseImage
{
    protected static void Run(ClientPipelineArgs args)
    {
        Assert.ArgumentNotNull((object)args, nameof(args));
        Item itemNotNull = Client.GetItemNotNull(args.Parameters["itemid"], Language.Parse(args.Parameters["language"]));
        itemNotNull.Fields.ReadAll();
        Field field = itemNotNull.Fields[args.Parameters["fieldid"]];
        Assert.IsNotNull((object)field, "field");
        ImageField imageField = new ImageField(field, field.Value);
        string parameter = args.Parameters["controlid"];
        string xml = args.Parameters["fieldValue"];
        if (args.IsPostBack)
        {
            if (!(args.Result != "undefined"))
                return;
            string empty;
            if (!string.IsNullOrEmpty(args.Result))
            {
                MediaItem mediaItem = (MediaItem)Client.ContentDatabase.GetItem(args.Result);
                if (mediaItem != null)
                {
                    imageField.SetAttribute("mediaid", mediaItem.ID.ToString());
                    if (xml.Length > 0)
                    {
                        XmlValue xmlValue = new XmlValue(xml, "image");
                        string attribute1 = xmlValue.GetAttribute("height");
                        if (!string.IsNullOrEmpty(attribute1))
                            imageField.Height = attribute1;
                        string attribute2 = xmlValue.GetAttribute("width");
                        if (!string.IsNullOrEmpty(attribute2))
                            imageField.Width = attribute2;
                    }
                }
                else
                    SheerResponse.Alert("Item not found.");
                empty = imageField.Value;
            }
            else
                empty = string.Empty;
            SheerResponse.SetAttribute("scHtmlValue", "value", WebEditImageCommand.RenderImage(args, empty));
            SheerResponse.SetAttribute("scPlainValue", "value", empty);
            SheerResponse.Eval("scSetHtmlValue('" + parameter + "')");
        }
        else
        {
            string str = StringUtil.GetString(field.Source, "/sitecore/media library");

            //service for multisite path resolution
            var MultisiteService = LocatorContainerManager.Current.Resolve<IMultisiteService>();

            //only continue if in Multisite context
            if (MultisiteService.IsMultisiteContext(itemNotNull.Paths.FullPath))
            {
                var siteName = MultisiteService.GetSiteName(itemNotNull.Paths.FullPath);
                var MultisitePath = MultisiteService.GetMultisiteComponentFolderPath(str, siteName, "");

                //check folder exists 
                var MultisitePathItem = itemNotNull.Database.GetItem(MultisitePath);
                if (MultisitePathItem != null)
                {
                    str = MultisitePath;
                }
                else
                {
                    //return the parent folder instead
                    str = MultisiteService.GetParentFolderPath(MultisitePath);
                }
            }

            if (xml.Length > 0)
                xml = new XmlValue(xml, "image").GetAttribute("mediaid");
            string path = xml;
            if (str.StartsWith("~", StringComparison.InvariantCulture))
            {
                if (string.IsNullOrEmpty(path))
                    path = StringUtil.Mid(str, 1);
                str = "/sitecore/media library";
            }
            Language language = itemNotNull.Language;
            MediaBrowserOptions mediaBrowserOptions = new MediaBrowserOptions();
            Item obj1 = Client.ContentDatabase.GetItem(str, language);
            if (obj1 == null)
                throw new ClientAlertException("The source of this Image field points to an item that does not exist.");
            mediaBrowserOptions.Root = obj1;
            mediaBrowserOptions.AllowEmpty = true;
            if (!string.IsNullOrEmpty(path))
            {
                Item obj2 = Client.ContentDatabase.GetItem(path, language);
                if (obj2 != null)
                    mediaBrowserOptions.SelectedItem = obj2;
            }
            SheerResponse.ShowModalDialog(mediaBrowserOptions.ToUrlString().ToString(), "1200px", "700px", string.Empty, true);
            args.WaitForPostBack();
        }
    }
}

note: please replace the Multisite Service and methods with your own.

Config Patch:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <commands>
      <command name="webedit:chooseimage" type="MyWebsite.Custom.Pipelines.MultisiteChooseImage, MyWebsite.Custom" />
    </commands>
  </sitecore>
</configuration>

The article linked above has a config patch for the field type as well but as the reference is directly changed in the Core DB it should work without a config patch.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.