/// <summary> /// Render and possibly publish an ECL item /// </summary> /// <param name="linkInfo">Parsed link info</param> /// <param name="item">EclItemInPackage object</param> /// <returns>template fragment for (published) ECL item or fragment with external link</returns> private string RenderEclItem(ParsedLinkInfo linkInfo, EclItemInPackage item) { // parse remaining attributes to determine width and height IDictionary<string, string> remainingAttributes = new Dictionary<string, string>(); string width; string height; ParseRemainingAttributes(linkInfo.RemainingAttributes, remainingAttributes, out width, out height); // convert remaining attributes into IList<ITemplateAttribute> IList<ITemplateAttribute> attributes = new List<ITemplateAttribute>(); foreach (var attribute in remainingAttributes) { attributes.Add(new NodeKeyValuePair(new KeyValuePair<string, string>(attribute.Key, attribute.Value))); } // we need a variantId because we could have to publish the ECL item using AddBinary string variantId = DefaultVariantId; if (!string.IsNullOrEmpty(linkInfo.VariantId)) { variantId = linkInfo.VariantId; } else if (!(string.IsNullOrEmpty(width) && !(string.IsNullOrEmpty(height)))) { variantId = string.Format("{0}x{1}", width, height); } string itemUrl = ResolveEclItem(item, attributes, variantId, _targetStructureGroup); attributes.Add(new NodeKeyValuePair(new KeyValuePair<string, string>(linkInfo.TargetAttribute, itemUrl))); if (linkInfo.LinkElementName.ToUpperInvariant() == "IMG") { string embedFragment = item.EclItem.GetTemplateFragment(attributes); if (!string.IsNullOrEmpty(embedFragment)) { // if provider can embed item we return embed fragment return embedFragment; } string externalLink = item.EclItem.GetDirectLinkToPublished(attributes); if (!string.IsNullOrEmpty(externalLink)) { StringBuilder result = new StringBuilder(); result.AppendFormat("<img "); foreach (var att in attributes) { string value = att.Value; if (att.Name.ToUpperInvariant() == "ALT") { value = item.EclItem.Title; } result.AppendFormat(" {0}=\"{1}\"", att.Name, HttpUtility.HtmlAttributeEncode(value)); } result.AppendFormat(" />"); return result.ToString(); } } // All <a> links and images that do not need special treatment processed by default finish actions // because we already resolve Ecl items with ResolveEclItem return null; }
/// <summary> /// ITemplate.Transform implementation /// </summary> /// <param name="engine">The engine can be used to retrieve additional information or execute additional actions</param> /// <param name="package">The package provides the primary data for the template, and acts as the output</param> public void Transform(Engine engine, Package package) { _engine = engine; _package = package; _publicationId = GetPublicationId(); // determine (optional) structure group parameter Item targetStructureGroupItem = _package.GetByName(ParameterNameTargetStructureGroup); if (targetStructureGroupItem != null) { if (!TcmUri.IsValid(targetStructureGroupItem.GetAsString())) { throw new InvalidDataException(string.Format("Target location {0} is not a structure group", targetStructureGroupItem)); } TcmUri uri = new TcmUri(targetStructureGroupItem.GetAsString()); // get target structure group in context publication _targetStructureGroup = (StructureGroup)_engine.GetSession().GetObject(new TcmUri(uri.ItemId, uri.ItemType, _publicationId)); Log.Debug(string.Format("Target Structure Group {0} found", _targetStructureGroup.Id)); } // make sure eclSession is disposed at the end using (IEclSession eclSession = SessionFactory.CreateEclSession(engine.GetSession())) { // loop over all ecl items and store them in a dictionary _eclItemsInPackage = new Dictionary<TcmUri, EclItemInPackage>(); foreach (Item item in _package.GetAllByType(new ContentType(EclContentType))) { if (item.Properties.ContainsKey(Item.ItemPropertyTcmUri) && item.Properties.ContainsKey(Item.ItemPropertyFileName)) { // has the item already been published? if (!item.Properties.ContainsKey(Item.ItemPropertyPublishedPath)) { // find ecl items string uri; if (item.Properties.TryGetValue(Item.ItemPropertyTcmUri, out uri)) { TcmUri stubUri = new TcmUri(uri); IEclUri eclUri = eclSession.TryGetEclUriFromTcmUri(stubUri); if (eclUri != null) { // we have a valid ecl item, lets store it in the dictionary IContentLibraryMultimediaItem eclItem = (IContentLibraryMultimediaItem)eclSession.GetContentLibrary(eclUri).GetItem(eclUri); EclItemInPackage eclItemInPackage = new EclItemInPackage(item, eclItem); _eclItemsInPackage.Add(stubUri, eclItemInPackage); } } } else { // items have been published, we are probabaly called too late... Log.Warning("{0} has already been published, \"Resolve ECL items\" should be added before \"Publish Binaries in Package\""); } } } // end processing if there are no ecl items found in the package if (_eclItemsInPackage.Count == 0) return; // determine item name to operate on from a parameter or use default string itemName = _package.GetValue(ParameterNameItemName); if (string.IsNullOrEmpty(itemName)) { itemName = Package.OutputName; } Item selectedItem = _package.GetByName(itemName); // continue on the output by matching the found ecl items in there against the list of ecl items in the package if ((selectedItem.Type == PackageItemType.String) || (selectedItem.Type == PackageItemType.Stream)) { // Assume text content string inputToConvert = selectedItem.GetAsString(); string convertedOutput = ResolveEclLinks(eclSession, inputToConvert); if (convertedOutput != null) { Log.Debug("Changed Output item"); selectedItem.SetAsString(convertedOutput); } } else { // XML XmlDocument outputDocument = selectedItem.GetAsXmlDocument(); ResolveEclLinks(eclSession, outputDocument); selectedItem.SetAsXmlDocument(outputDocument); } } }
/// <summary> /// Resolve and possibly publish an ECL item /// </summary> /// <param name="eclItemInPackage">EclItemInPackage object</param> /// <param name="attributes">template atrributes</param> /// <param name="variantId">variant id</param> /// <param name="targetStructureGroup">target Structure Group to publish too (when null, Image Structure Group from Publication will be used</param> /// <returns>tridion publish path or external link</returns> private string ResolveEclItem(EclItemInPackage eclItemInPackage, IList<ITemplateAttribute> attributes, string variantId, StructureGroup targetStructureGroup) { // determine if item is already published or if we should get the content for it and let tridion publish it string publishedPath = eclItemInPackage.EclItem.GetDirectLinkToPublished(attributes); if (string.IsNullOrEmpty(publishedPath)) { // tridion must publish this ecl item as a variant Component component = (Component)_engine.GetSession().GetObject(eclItemInPackage.StubUri); // create a filename with size and a proper extension string filename = ConstructFileName(eclItemInPackage.EclItem.Filename, variantId, eclItemInPackage.EclUri.ToString()); Binary publishedEclItem; // get content as stream Stream contentStream = eclItemInPackage.PackageItem.GetAsStream(); string contentType = eclItemInPackage.EclItem.MimeType; using (contentStream) { // a provider (like the ADAM provider) can return an empty stream when requested without attributes // in that case we should resolve the item with its size taken into consideration // so here we ask for the content again passing width and height in attributes if (contentStream.Length == 0) { using (Stream stream = CreateTemporaryFileStream()) { IContentResult content = eclItemInPackage.EclItem.GetContent(attributes); content.Stream.CopyTo(stream); publishedEclItem = AddBinary(stream, variantId, contentType, filename, component, targetStructureGroup); } } else { publishedEclItem = AddBinary(contentStream, variantId, contentType, filename, component, targetStructureGroup); } } // we could consider updating the package item with the content and the file extension, but to change the content type, we have to recreate it // this won't be of much use other than getting the content stream twice (performance impact) as the package item will (or should) not be used after this anymore // set published path in package item and add to attributes eclItemInPackage.PackageItem.Properties[Item.ItemPropertyPublishedPath] = publishedEclItem.Url; return publishedEclItem.Url; } // ecl item is already published, lets set the uri that can be used from a public website to link directly to the item on the external system eclItemInPackage.PackageItem.Properties[Item.ItemPropertyPublishedPath] = publishedPath; Log.Info(string.Format("resolved ECL item with path {0}", publishedPath)); return publishedPath; }
/// <summary> /// Resolve and possibly publish an ECL item /// </summary> /// <param name="eclItemInPackage">EclItemInPackage object</param> /// <param name="attributes">template atrributes</param> /// <param name="variantId">variant id</param> /// <param name="targetStructureGroup">target Structure Group to publish too (when null, Image Structure Group from Publication will be used</param> /// <returns>tridion publish path or external link</returns> private string ResolveEclItem(EclItemInPackage eclItemInPackage, IList<ITemplateAttribute> attributes, string variantId, StructureGroup targetStructureGroup) { // clear so a simple IsNull check can be used unless we actually set it _metadataEmbedCode = null; Component component = (Component)_engine.GetSession().GetObject(eclItemInPackage.StubUri); // assuming there is a program with and asset in the metadata, checking if that contains channelinfo (assuming that will be the youtube channel) XDocument metadata = XDocument.Parse(eclItemInPackage.EclItem.MetadataXml); if (metadata.Descendants(MmEclNs + "Program").Descendants(MmEclNs + "Asset").Descendants(MmEclNs + "ChannelInfo").Any()) { // get youtube id from ecl metadata string youTubeId = metadata.Descendants(MmEclNs + "Program").Descendants(MmEclNs + "Asset").Descendants(MmEclNs + "ChannelInfo").Descendants(MmEclNs + "Reference").FirstOrDefault().Value; if (!string.IsNullOrEmpty(youTubeId)) { // ToDo: make these configurable via parameters schema values const string YouTubeUrl = "//www.youtube.com/embed/"; const string YouTubeEmbedCode = "<iframe width=\"{1}\" height=\"{2}\" src=\"{0}\" frameborder=\"0\" allowfullscreen></iframe>"; const int DefaultWidth = 640; const int DefaultHeight = 360; const string DefaultLanguage = "us-EN"; // check if we need translated video string owningLanguageCode = string.Empty; string languageCode = string.Empty; ItemFields owningMetadataFields = new ItemFields(component.OwningRepository.Metadata, component.OwningRepository.MetadataSchema); if (owningMetadataFields.Contains(LanguageMetadataField)) { owningLanguageCode = ((KeywordField)owningMetadataFields[LanguageMetadataField]).Value.Title; } ItemFields metadataFields = new ItemFields(component.ContextRepository.Metadata, component.ContextRepository.MetadataSchema); if (metadataFields.Contains(LanguageMetadataField)) { languageCode = ((KeywordField)metadataFields[LanguageMetadataField]).Value.Title; } // check if not default language and context repository and owning repository have the same language codes (indicating the video is localized in the language publication) if (!languageCode.Equals(DefaultLanguage) && owningLanguageCode.Equals(languageCode)) { // get translation folder id string distributionId = string.Format("[{0}]", eclItemInPackage.EclUri.ItemId); // ToDo: reuse IEclSession from Transform method (and correctly dispose it) using (IEclSession eclSession = SessionFactory.CreateEclSession(_engine.GetSession())) { // ToDo: see if IContentLibraryContext can be reused through Transform method (and correctly disposed of) using (IContentLibraryContext context = eclSession.GetContentLibrary(eclItemInPackage.EclUri)) { // loop over all folders where item resides and use the one which begins with "[distributionId]" IFolderContent folderContent = context.GetFolderContent(eclItemInPackage.EclItem.ParentId, 0, EclItemTypes.Folder); foreach (IContentLibraryListItem subFolder in folderContent.ChildItems) { if (subFolder.Title.StartsWith(distributionId)) { // list all distributions in this folder and use the one which begins with "[languageCode]" string language = string.Format("[{0}]", languageCode); folderContent = context.GetFolderContent(subFolder.Id, 0, EclItemTypes.File); foreach (IContentLibraryListItem distribution in folderContent.ChildItems) { if (distribution.Title.StartsWith(language)) { IContentLibraryMultimediaItem translatedItem = (IContentLibraryMultimediaItem)context.GetItem(distribution.Id); // use translated video int width = translatedItem.Width.HasValue ? translatedItem.Width.Value : DefaultWidth; int height = translatedItem.Height.HasValue ? translatedItem.Height.Value : DefaultHeight; metadata = XDocument.Parse(translatedItem.MetadataXml); // assuming there is a program with and asset in the metadata, checking of that contains channelinfo (assuming that will be the youtube channel) if (metadata.Descendants(MmEclNs + "Program").Descendants(MmEclNs + "Asset").Descendants(MmEclNs + "ChannelInfo").Any()) { // get youtube id from ecl metadata youTubeId = metadata.Descendants(MmEclNs + "Program").Descendants(MmEclNs + "Asset").Descendants(MmEclNs + "ChannelInfo").Descendants(MmEclNs + "Reference").FirstOrDefault().Value; if (!string.IsNullOrEmpty(youTubeId)) { _metadataEmbedCode = string.Format(YouTubeEmbedCode, YouTubeUrl + youTubeId, width, height); } } break; } } break; } } } } } else { // use existing video int width = eclItemInPackage.EclItem.Width.HasValue ? eclItemInPackage.EclItem.Width.Value : DefaultWidth; int height = eclItemInPackage.EclItem.Height.HasValue ? eclItemInPackage.EclItem.Height.Value : DefaultHeight; _metadataEmbedCode = string.Format(YouTubeEmbedCode, YouTubeUrl + youTubeId, width, height); } return YouTubeUrl + youTubeId; } } // determine if item is already published or if we should get the content for it and let tridion publish it string publishedPath = eclItemInPackage.EclItem.GetDirectLinkToPublished(attributes); if (string.IsNullOrEmpty(publishedPath)) { // tridion must publish this ecl item as a variant // create a filename with size and a proper extension string filename = ConstructFileName(eclItemInPackage.EclItem.Filename, variantId, eclItemInPackage.EclUri.ToString()); Binary publishedEclItem; // get content as stream Stream contentStream = eclItemInPackage.PackageItem.GetAsStream(); string contentType = eclItemInPackage.EclItem.MimeType; using (contentStream) { // a provider (like the ADAM provider) can return an empty stream when requested without attributes // in that case we should resolve the item with its size taken into consideration // so here we ask for the content again passing width and height in attributes if (contentStream.Length == 0) { using (Stream stream = CreateTemporaryFileStream()) { IContentResult content = eclItemInPackage.EclItem.GetContent(attributes); content.Stream.CopyTo(stream); publishedEclItem = AddBinary(stream, variantId, contentType, filename, component, targetStructureGroup); } } else { publishedEclItem = AddBinary(contentStream, variantId, contentType, filename, component, targetStructureGroup); } } // we could consider updating the package item with the content and the file extension, but to change the content type, we have to recreate it // this won't be of much use other than getting the content stream twice (performance impact) as the package item will (or should) not be used after this anymore // set published path in package item and add to attributes eclItemInPackage.PackageItem.Properties[Item.ItemPropertyPublishedPath] = publishedEclItem.Url; return publishedEclItem.Url; } // ecl item is already published, lets set the uri that can be used from a public website to link directly to the item on the external system eclItemInPackage.PackageItem.Properties[Item.ItemPropertyPublishedPath] = publishedPath; Log.Info(string.Format("resolved ECL item with path {0}", publishedPath)); return publishedPath; }