Navigation and service panel


Content

This text is fallbacked from the German Version. If you need use Google Translate


Verlinken von Produkten

By Reto Hugi on 26. June 2013, No comments

Sitecore E-Commerce Services speichern Produkte in einem sog. Repository ausserhalb der Webseite. Dies ermöglicht eine für die Verwaltung geeignete Strukturierung zu erstellen (frei definierbare Ordnerstruktur für die Produkte), und dennoch unabhängig von der eigentlichen Informationsarchitektur zu bleiben. Was aber, wenn ein Autor ein Produkt resp. eine Produktseite direkt verlinken möchte?

Die Detailseiten von Produkten und damit auch deren URLs werden "dynamisch" erstellt und sind eine Kombination aus Site-Struktur und Produktnamen. Konkret ist es der VirtualProductResolver, der in Kombination mit einem Katalog die URL zum Produktdetail erstellt und der ProductResolver Processor in der HttpRequestBegin Pipeline, der für die Auflösung der URL zu Produkten zuständig ist.

Möchte ein Autor nun Produkt XY direkt verlinken (z.B. in einem Rich Text Feld oder einem General Link Feld) wird er einen Link auf das Produkt im Repository ausserhalb der Site machen, welcher via URL somit nicht erreichbar ist. (Und nein, Copy & Paste des Pfades von der publizierten Seite ist keine Option ;-))

Es bleiben 2 Möglichkeiten, die Verlinkung korrekt aufzulösen:

  • Modifizieren des LinkManagers
  • Einen Processor für die RenderField Pipeline schreiben

Zwei Hauptgründe haben uns dazu bewogen, die Variante RenderField Pipeline Processor zu wählen:

  • Der LinkManager kann nicht konfigurativ (z.B. Pipeline) erweitert werden, man überschreibt seine Methoden, wo notwendig. Bei vielen Erweiterungen wird die Wartbarkeit bei Upgrades aufwändig.
  • Die Erweiterung der RenderField Pipeline bringt weniger Komplexität mit sich und kann gezielt für die Felder / Feldtypen eingesetzt werden, die eine Erweiterung benötigen.

In der RenderField Pipeline werden nun Links zu Produkten mit korrekten Verlinkungen versehen. Was im folgenden Beispiel für Produkt URLs gemacht wird, funktioniert übrigens auch für Items, welche über Wilcard Items (*) verlinkt werden sollen (unabhängig der SES).

Es gibt zwei Processoren: Einen brauchen wir für Rich Text Felder und einer für General Link Felder. Der Processor für Rich Text Felder dient auch gleich als Vorbereitung für den anderen und muss direkt nach dem Processor Sitecore.Pipelines.RenderField.GetFieldValue eingefügt werden:

namespace Unic.Examples.Pipelines.RenderField
{
    using System;
    using System.Text;
    using Sitecore.Data;
    using Sitecore.Data.Fields;
    using Sitecore.Data.Items;
    using Sitecore.Links;
    using Sitecore.Pipelines.RenderField;

    /// <summary>
    /// Processor to replace all links to wildcard and product items
    /// </summary>
    public class RewriteRichTextLinks
    {
        /// <summary>
        /// The link start identifier
        /// </summary>
        private const string LINK_START = "∼/link.aspx?";

        /// <summary>
        /// What is appended to a url in the rich text field
        /// </summary>
        private const string URL_APPENDER = "_z=z";

        /// <summary>
        /// Processes the specified args.
        /// </summary>
        /// <param name="args">The args.</param>
        public void Process(RenderFieldArgs args)
        {
            Sitecore.Diagnostics.Assert.ArgumentNotNull(args, "args");
            if (Sitecore.Context.PageMode.IsPageEditorEditing)
            {
                return;
            }

            if (string.IsNullOrEmpty(args.Result.FirstPart) && string.IsNullOrEmpty(args.Result.LastPart))
            {
                return;
            }

            if (args.FieldTypeKey == "rich text")
            {
                args.Result.FirstPart = this.ReplaceRichText(args.Result.FirstPart);
                args.Result.LastPart = this.ReplaceRichText(args.Result.LastPart);
            }
            else if (args.FieldTypeKey == "general link")
            {
                LinkField field = new LinkField(args.GetField());
                if (field.LinkType == "internal")
                {
                    args.CustomData["targetItem"] = field.TargetItem;
                }
            }
        }

        /// <summary>
        /// Loop through each link and replace it with a link to the correct item.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>new output string</returns>
        private string ReplaceRichText(string input)
        {
            int startIndex = input.IndexOf(LINK_START, StringComparison.InvariantCulture);
            int endIndex = 0;

            StringBuilder stringBuilder = new StringBuilder(input.Length);

            // loop through all of the links within the text and replace product links
            for (; startIndex >= 0; startIndex = input.IndexOf(LINK_START, endIndex, StringComparison.InvariantCulture))
            {
                // get the end of the id string
                int endOfId = input.IndexOf(URL_APPENDER, startIndex, StringComparison.InvariantCulture);

                // parse out the id from the link text
                ID id = DynamicLink.Parse(input.Substring(startIndex, endOfId - startIndex)).ItemId;

                // default the url to the original id link
                string url = LINK_START + "_id=" + id.ToShortID() + "&" + URL_APPENDER;

                // get the item for the id
                Item referencedItem = Sitecore.Context.Database.GetItem(id);
                if (referencedItem != null)
                {
                    string tempUrl = this.GetProductUrl(referencedItem);
                    if (!string.IsNullOrWhiteSpace(tempUrl))
                    {
                        url = tempUrl;
                    }
                }

                // append together the text and url and add it to the stringbuilder
                string urlString = input.Substring(endIndex, startIndex - endIndex);
                stringBuilder.Append(urlString);
                stringBuilder.Append(url);
                endIndex = endOfId + URL_APPENDER.Length;
            }

            // add end of text
            stringBuilder.Append(input.Substring(endIndex));

            // return the string builder that contains the new links
            return stringBuilder.ToString();
        }
    }
}

Der zweite Processor für General Link Felder muss direkt vor Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues eingefügt werden. Das verlinkte Item wird bereits im vorherigen Processor zwischengespeichert und somit muss hier nur noch der Link umgeschrieben werden:

 namespace Unic.Examples.Pipelines.RenderField
    {
      using System.Text.RegularExpressions;
      using Sitecore.Data.Items;
      using Sitecore.Pipelines.RenderField;

      /// <summary>
      /// Rewrite links to wildcard item in general link fields.
      /// </summary>
      public class RewriteGeneralLinks
      {
        /// <summary>
        /// Processes the specified args.
        /// </summary>
        /// <param name="args">The args.</param>
        public void Process(RenderFieldArgs args)
        {
          Sitecore.Diagnostics.Assert.ArgumentNotNull(args, "args");
          if (Sitecore.Context.PageMode.IsPageEditorEditing)
          {
            return;
          }

          if (string.IsNullOrEmpty(args.Result.FirstPart) &amp;&amp; string.IsNullOrEmpty(args.Result.LastPart))
          {
            return;
          }

          if (args.FieldTypeKey == "general link")
          {
            Item targetItem = args.CustomData["targetItem"] as Item;
            if (targetItem != null)
            {
              args.Result.FirstPart = this.ReplaceGeneralLink(args.Result.FirstPart, targetItem);
              args.Result.LastPart = this.ReplaceGeneralLink(args.Result.LastPart, targetItem);
            }
          }
        }

        /// <summary>
        /// Replaces the general link value.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="targetItem">The target item.</param>
        /// <returns>Replaced link</returns>
        private string ReplaceGeneralLink(string input, Item targetItem)
        {
          if (!string.IsNullOrWhiteSpace(input) &amp;&amp; input.IndexOf("href=") > -1 &amp;&amp; targetItem != null)
          {
            string url = GetProductUrl(targetItem);
            if (!string.IsNullOrWhiteSpace(url))
            {
              return Regex.Replace(input, "href=\"(.*)\"", string.Format("href=\"{0}\"", url));
            }
          }

          return input;
        }
      }
    }

Die Methode GetProductUrl lädt das Produkt via GetProduct und gibt die URL der Produktdetailseite zurück:

/// Gets the URL for a product.
/// </summary>
/// <param name="item">The product item.</param>
/// <returns>The product URL</returns>
private static string GetProductUrl(Item item)
{
    Assert.ArgumentNotNull(item, "item");
    ProductBase product = ProductUtil.GetProduct(item);
    if (product != null &amp;&amp; !string.IsNullOrWhiteSpace(product.Url))
    {
        return product.Url;
    }

    return string.Empty;
}

Das Url Property des Produktes muss über den Katalog-Index abgefragt werden, da wir uns nicht zwingend innerhalb eines Kataloges befinden. Siehe dazu den Beitrag Produkte Suchen. Die Methode GetProduct nutzt die SES API um das Produkt aus dem Repository zu laden:

/// <summary>
    /// Gets the product based on an Item.
    /// </summary>
    /// <typeparam name="TProduct">The type of the product.</typeparam>
    /// <param name="productItem">The product item.</param>
    /// <returns>the mapped product instance</returns>
    public static TProduct GetProduct<TProduct>(Item productItem) where TProduct : IProductRepositoryItem
    {
        try
        {
            Assert.ArgumentNotNull(productItem, "productItem");
            IProductRepository productRepository = Sitecore.Ecommerce.Context.Entity.Resolve<IProductRepository>();
            return productRepository.Get<TProduct>(productItem.ID.ToString());
        }
        catch (Exception ex)
        {
            Sitecore.Diagnostics.Log.Error("Error while loading product", ex, typeof(ProductUtil));
        }

        return default(TProduct);
    }

Credits: Code Teile von Kevin Brechbühl und Liz Spranzani (via SDN).

No comments

Add your comment

Your email address will not be published. Required fields are marked *

*