From 21f92ce7aac6c6b12d507e2bf92dfc3cad297327 Mon Sep 17 00:00:00 2001 From: Jeroen Suurd Date: Tue, 24 Mar 2015 08:39:43 -0700 Subject: [PATCH 1/5] Added check if components metadata is null to prevent NullReferenceException if the component has no metadata --- .../Sdl.Web.Templating/Templates/ResolveRichText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content-management/Sdl.Web.Templating/Templates/ResolveRichText.cs b/content-management/Sdl.Web.Templating/Templates/ResolveRichText.cs index b306d6d..358b4de 100644 --- a/content-management/Sdl.Web.Templating/Templates/ResolveRichText.cs +++ b/content-management/Sdl.Web.Templating/Templates/ResolveRichText.cs @@ -69,7 +69,7 @@ private string ResolveXhtml(string input) { Component comp = (Component)Engine.GetObject(uri); // resolve youtube video - if (comp != null) + if (comp != null && comp.Metadata != null) { ItemFields fields = new ItemFields(comp.Metadata, comp.MetadataSchema); ProcessFields(fields, link); From 8fbc04af2de9d5431f46e9a4509d156b48bf280d Mon Sep 17 00:00:00 2001 From: Jeroen Suurd Date: Tue, 24 Mar 2015 08:51:56 -0700 Subject: [PATCH 2/5] Only strip the URL if it has the default extension to support external URL's in redirects --- .../Sdl.Web.Common/Models/Navigation/SitemapItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web-application/Sdl.Web.Common/Models/Navigation/SitemapItem.cs b/web-application/Sdl.Web.Common/Models/Navigation/SitemapItem.cs index af0e7de..bd0f4e4 100644 --- a/web-application/Sdl.Web.Common/Models/Navigation/SitemapItem.cs +++ b/web-application/Sdl.Web.Common/Models/Navigation/SitemapItem.cs @@ -6,6 +6,9 @@ namespace Sdl.Web.Common.Models { public class SitemapItem : EntityBase { + //TODO: Make this configurable since its dependend on framework implementation + private const string DefaultExtension = ".html"; + private string _url; public SitemapItem() @@ -29,7 +32,7 @@ public string Url private static string ProcessUrl(string value) { - return Path.HasExtension(value) ? value.Substring(0, value.Length - Path.GetExtension(value).Length) : value; + return Path.HasExtension(value) && DefaultExtension.Equals(Path.GetExtension(value)) ? value.Substring(0, value.Length - Path.GetExtension(value).Length) : value; } public string Type { get; set; } From 38cc8d369fced0634ce69d48d4483c8ecc1a25bf Mon Sep 17 00:00:00 2001 From: Jeroen Suurd Date: Wed, 22 Jul 2015 11:00:50 -0700 Subject: [PATCH 3/5] Also strip the forward slash when resolved link is default page for consistency --- web-application/Sdl.Web.DD4T/Mapping/DD4TContentResolver.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web-application/Sdl.Web.DD4T/Mapping/DD4TContentResolver.cs b/web-application/Sdl.Web.DD4T/Mapping/DD4TContentResolver.cs index bd9f4fe..ea66661 100644 --- a/web-application/Sdl.Web.DD4T/Mapping/DD4TContentResolver.cs +++ b/web-application/Sdl.Web.DD4T/Mapping/DD4TContentResolver.cs @@ -62,9 +62,11 @@ public virtual string ResolveLink(object linkData, object resolveInstruction = n if (url!=null && url.EndsWith(DefaultExtension)) { url = url.Substring(0, url.Length - DefaultExtension.Length); - if (url.EndsWith("/" + DefaultExtensionLessPageName)) + // Also strip the forward slash + var defaultPageNamePart = "/" + DefaultExtensionLessPageName; + if (url.EndsWith(defaultPageNamePart)) { - url = url.Substring(0, url.Length - DefaultExtensionLessPageName.Length); + url = url.Substring(0, url.Length - defaultPageNamePart.Length); } } } From 058ff022f04c0541bd6da761ff97c409697a9cb9 Mon Sep 17 00:00:00 2001 From: Jeroen Suurd Date: Tue, 28 Jul 2015 10:00:36 -0700 Subject: [PATCH 4/5] Adding Media Manager module for STRI v1.0.1 --- content-management/.gitignore | 10 +- .../Sdl.Web.Templating.MediaManager.sln | 22 + .../Common/ItemFieldsExtensions.cs | 164 ++++++ .../Common/ObjectExtensions.cs | 30 ++ .../Common/TemplateBase.cs | 499 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++ .../Sdl.Web.Templating.MediaManager.csproj | 80 +++ .../ResolveMediaManagerReferences.cs | 90 ++++ .../_references/ecl/missing_files.md | 6 + modules/MediaManager-Module-ContentPort.zip | Bin 0 -> 21978 bytes 10 files changed, 933 insertions(+), 4 deletions(-) create mode 100644 content-management/Sdl.Web.Templating.MediaManager.sln create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Common/ItemFieldsExtensions.cs create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Common/ObjectExtensions.cs create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Common/TemplateBase.cs create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Properties/AssemblyInfo.cs create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Sdl.Web.Templating.MediaManager.csproj create mode 100644 content-management/Sdl.Web.Templating.MediaManager/Templates/ResolveMediaManagerReferences.cs create mode 100644 content-management/_references/ecl/missing_files.md create mode 100644 modules/MediaManager-Module-ContentPort.zip diff --git a/content-management/.gitignore b/content-management/.gitignore index d0a3110..3ad80a8 100644 --- a/content-management/.gitignore +++ b/content-management/.gitignore @@ -1,4 +1,6 @@ -/Sdl.Web.Templating/bin -/Sdl.Web.Templating/obj -/Sdl.Web.Templating/*.user -/Sdl.Web.Templating/*.suo +bin +obj +*.user +*.suo +_references/2013-sp1/*.dll +_references/ecl/*.dll diff --git a/content-management/Sdl.Web.Templating.MediaManager.sln b/content-management/Sdl.Web.Templating.MediaManager.sln new file mode 100644 index 0000000..d3ce49c --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.Web.Templating.MediaManager", "Sdl.Web.Templating.MediaManager\Sdl.Web.Templating.MediaManager.csproj", "{4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/content-management/Sdl.Web.Templating.MediaManager/Common/ItemFieldsExtensions.cs b/content-management/Sdl.Web.Templating.MediaManager/Common/ItemFieldsExtensions.cs new file mode 100644 index 0000000..b881a6d --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Common/ItemFieldsExtensions.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Tridion.ContentManager.ContentManagement; +using Tridion.ContentManager.ContentManagement.Fields; + +namespace Sdl.Web.Tridion.Common +{ + public static class ItemFieldsExtensions + { + public static double GetNumberValue(this ItemFields fields, string fieldName) + { + return fields.GetNumberValues(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetNumberValues(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as NumberField).Values + : new double[0]; + } + + public static Keyword GetKeywordValue(this ItemFields fields, string fieldName) + { + return fields.GetKeywordValues(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetKeywordValues(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as KeywordField).Values + : Enumerable.Empty(); + } + + public static Component GetComponentValue(this ItemFields fields, string fieldName) + { + return fields.GetComponentValues(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetComponentValues(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as ComponentLinkField).Values + : Enumerable.Empty(); + } + + public static string GetExternalLink(this ItemFields fields, string fieldName) + { + return fields.GetExternalLinks(fieldName).FirstOrDefault() ?? string.Empty; + } + + public static IEnumerable GetExternalLinks(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as ExternalLinkField).Values + : new string[0]; + } + + public static Component GetMultimediaLink(this ItemFields fields, string fieldName) + { + return fields.GetMultimediaLinks(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetMultimediaLinks(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as MultimediaLinkField).Values + : Enumerable.Empty(); + } + + public static ItemFields GetEmbeddedField(this ItemFields fields, string fieldName) + { + return fields.GetEmbeddedFields(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetEmbeddedFields(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as EmbeddedSchemaField).Values + : Enumerable.Empty(); + } + + public static string GetTextValue(this ItemFields fields, string fieldName) + { + return GetTextValues(fields, fieldName).FirstOrDefault() ?? string.Empty; + } + + public static IEnumerable GetTextValues(this ItemFields fields, string fieldName) + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as TextField).Values + : new string[0]; + } + + public static DateTime? GetDateValue(this ItemFields fields, string fieldName = "date") + { + return fields.GetDateValues(fieldName).FirstOrDefault(); + } + + public static IEnumerable GetDateValues(this ItemFields fields, string fieldName = "date") + { + return + null != fields && fields.Contains(fieldName) + ? (fields[fieldName] as DateField).Values.Select(d => d == DateTime.MinValue ? null : (DateTime?)d) + : new DateTime?[0]; + } + + public static int GetFieldValueCount(this ItemFields fields, string fieldName) + { + if (null == fields) + { + return 0; + } + + var field = fields[fieldName]; + + return + field is ComponentLinkField + ? (field as ComponentLinkField).Values.Count + : field is TextField + ? (field as TextField).Values.Count + : field is EmbeddedSchemaField + ? (field as EmbeddedSchemaField).Values.Count + : 0; + } + + /// + /// Manual unification of different field types logic to overcome native tridion implementation shortcoming, + /// which is not polymorphic. + /// + static readonly IDictionary> ValueResolver = + new Dictionary> { + { typeof(KeywordField), (fields, name) => fields.GetKeywordValues(name).Select(k => k.Title).FirstOrDefault() }, + { typeof(ComponentLinkField), (fields, name) => fields.GetComponentValues(name).Select(c => c.Id).FirstOrDefault() }, + { typeof(ExternalLinkField), (fields, name) => fields.GetExternalLinks(name).FirstOrDefault() }, + { typeof(MultimediaLinkField), (fields, name) => fields.GetMultimediaLinks(name).Select(mc => mc.Title).FirstOrDefault() }, + { typeof(DateField), (fields, name) => ((DateTime)fields.GetDateValues(name).FirstOrDefault()).ToString("yyyy-MM-dd HH:mm:ss") } + }; + + /// + /// Gets a sensible string represntation of a field. + /// + public static string GetSingleFieldValue(this ItemFields fields, string fieldName) + { + ItemField field; + Type fieldType; + + return + null == fields + || !fields.Contains(fieldName) + ? String.Empty + : ValueResolver.ContainsKey((fieldType = (field = fields[fieldName]).GetType())) + ? ValueResolver[fieldType](fields, fieldName) ?? String.Empty + : field.ToString(); + } + } +} diff --git a/content-management/Sdl.Web.Templating.MediaManager/Common/ObjectExtensions.cs b/content-management/Sdl.Web.Templating.MediaManager/Common/ObjectExtensions.cs new file mode 100644 index 0000000..119e59d --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Common/ObjectExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Sdl.Web.Tridion.Common +{ + public static class ObjectExtensions + { + public static IEnumerable IfNotNull(this TInput value, Func> getResult) + { + // TODO possible compare of value type with null (http://confluence.jetbrains.com/display/ReSharper/Possible+compare+of+value+type+with+null) + return null != value ? getResult(value) : Enumerable.Empty(); + } + + public static TOutput IfNotNull(this TInput value, Func getResult) + { + // TODO possible compare of value type with null + return null != value ? getResult(value) : default(TOutput); + } + + public static void IfNotNull(this TInput value, Action action) + { + // TODO possible compare of value type with null + if (null != value) + { + action(value); + } + } + } +} diff --git a/content-management/Sdl.Web.Templating.MediaManager/Common/TemplateBase.cs b/content-management/Sdl.Web.Templating.MediaManager/Common/TemplateBase.cs new file mode 100644 index 0000000..1706cfd --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Common/TemplateBase.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web.Script.Serialization; +using System.Xml; +using Tridion.ContentManager; +using Tridion.ContentManager.CommunicationManagement; +using Tridion.ContentManager.ContentManagement; +using Tridion.ContentManager.ContentManagement.Fields; +using Tridion.ContentManager.Publishing; +using Tridion.ContentManager.Publishing.Rendering; +using Tridion.ContentManager.Templating; +using Tridion.ContentManager.Templating.Assembly; + +namespace Sdl.Web.Tridion.Common +{ + /// + /// Base class for common functionality used by TBBs + /// + public abstract class TemplateBase : ITemplate + { + protected Engine Engine; + protected Package Package; + protected int RenderContext = -1; + + protected const string JsonMimetype = "application/json"; + protected const string JsonExtension = ".json"; + protected const string BootstrapFilename = "_all"; + + protected TemplatingLogger Logger + { + get + { + if (_mLogger == null) + { + _mLogger = TemplatingLogger.GetLogger(GetType()); + } + + return _mLogger; + } + } + + private TemplatingLogger _mLogger; + + /// + /// Initializes the engine and package to use in this TemplateBase object. + /// + /// The engine to use in calls to the other methods of this TemplateBase object + /// The package to use in calls to the other methods of this TemplateBase object + protected void Initialize(Engine engine, Package package) + { + Engine = engine; + Package = package; + } + + public virtual void Transform(Engine engine, Package package) { } + + /// + /// Checks whether the TemplateBase object has been initialized correctly. + /// This method should be called from any method that requires the m_Engine, + /// m_Package or _log member fields. + /// + protected void CheckInitialized() + { + if (Engine == null || Package == null) + { + throw new InvalidOperationException("This method can not be invoked, unless Initialize has been called"); + } + } + + #region Get context objects and information + + /// + /// True if the rendering context is a page, rather than component + /// + public bool IsPageTemplate() + { + + if (RenderContext == -1) + { + if (Engine.PublishingContext.ResolvedItem.Item is Page) + { + RenderContext = 1; + } + else + { + RenderContext = 0; + } + } + + return RenderContext == 1; + } + + /// + /// Returns the component object that is defined in the package for this template. + /// + /// + /// This method should only be called when there is an actual Component item in the package. + /// It does not currently handle the situation where no such item is available. + /// + /// the component object that is defined in the package for this template. + public Component GetComponent() + { + CheckInitialized(); + Item component = Package.GetByName(Package.ComponentName); + if (component != null) + { + return (Component)Engine.GetObject(component.GetAsSource().GetValue("ID")); + } + + return null; + } + + /// + /// Returns the Template from the resolved item if it's a Component Template + /// + /// A Component Template or null + protected ComponentTemplate GetComponentTemplate() + { + CheckInitialized(); + Template template = Engine.PublishingContext.ResolvedItem.Template; + + // "if (template is ComponentTemplate)" might work instead + if (template.GetType().Name.Equals(Package.ComponentTemplateName)) + { + return (ComponentTemplate)template; + } + + return null; + } + + /// + /// Returns the page object that is defined in the package for this template. + /// + /// + /// This method should only be called when there is an actual Page item in the package. + /// It does not currently handle the situation where no such item is available. + /// + /// the page object that is defined in the package for this template. + public Page GetPage() + { + CheckInitialized(); + + //first try to get from the render context + RenderContext renderContext = Engine.PublishingContext.RenderContext; + if (renderContext != null) + { + Page contextPage = renderContext.ContextItem as Page; + if (contextPage != null) + { + return contextPage; + } + } + + Item pageItem = Package.GetByType(ContentType.Page); + if (pageItem != null) + { + return (Page)Engine.GetObject(pageItem.GetAsSource().GetValue("ID")); + } + + return null; + } + + /// + /// Returns the publication object that can be determined from the package for this template. + /// + /// + /// This method currently depends on a Page item being available in the package, meaning that + /// it will only work when invoked from a Page Template. + /// + /// + /// the Publication object that can be determined from the package for this template. + protected Publication GetPublication() + { + CheckInitialized(); + + RepositoryLocalObject pubItem; + Repository repository = null; + + if (Package.GetByType(ContentType.Page) != null) + { + pubItem = GetPage(); + } + else + { + pubItem = GetComponent(); + } + + if (pubItem != null) + { + repository = pubItem.ContextRepository; + } + + return repository as Publication; + } + + protected bool IsPublishingToStaging() + { + if (Engine.PublishingContext != null && Engine.PublishingContext.PublicationTarget != null) + { + return Engine.PublishingContext.PublicationTarget != null && Engine.PublishingContext.PublicationTarget.Title.ToLower().Contains("staging"); + } + return false; + } + + protected bool IsPreviewMode() + { + return Engine.RenderMode == RenderMode.PreviewDynamic || Engine.RenderMode == RenderMode.PreviewStatic; + } + + protected bool IsMasterWebPublication() + { + var publication = GetPublication(); + //TODO - possibly need to extend with metadata or something for the case we have a non-published parent and all children at same level - one publication should be leading + if (publication.PublicationUrl == "" || publication.PublicationUrl == "/") + { + return true; + } + //Not valid for all blueprints + //Logger.Debug("publication url is not / publication has children? " + publication.HasChildren); + //return publication.HasChildren; + return false; + } + + #endregion + + #region Useful Bits and Pieces + + /// + /// Put the context component back on top of the package stack + /// As some TBBs (like SiteEdit ones) rely on this being the first + /// Component in the stack + /// + protected void PutContextComponentOnTop() + { + Item mainComponent = Package.GetByName("Component"); + if (mainComponent != null) + { + Package.Remove(mainComponent); + Package.PushItem("Component", mainComponent); + } + } + + + #endregion + + #region TOM.NET Helper functions + protected List> GetOrganizationalItemContents(OrganizationalItem orgItem, ItemType itemType, bool recursive) + { + var filter = new OrganizationalItemItemsFilter(orgItem.Session) + { + ItemTypes = new List { itemType }, + Recursive = recursive + }; + return XmlElementToTcmUriList(orgItem.GetListItems(filter)); + } + + protected OrganizationalItem GetChildOrganizationalItem(OrganizationalItem root, string title) + { + foreach (var child in GetOrganizationalItemContents(root, root is Folder ? ItemType.Folder : ItemType.StructureGroup, false)) + { + if (child.Value.ToLower() == title.ToLower()) + { + return (OrganizationalItem)Engine.GetObject(child.Key); + } + } + return null; + } + + protected List> GetUsingItems(RepositoryLocalObject subject, ItemType itemType) + { + UsingItemsFilter filter = new UsingItemsFilter(Engine.GetSession()) + { + ItemTypes = new List { itemType }, + BaseColumns = ListBaseColumns.IdAndTitle + }; + return XmlElementToTcmUriList(subject.GetListUsingItems(filter)); + } + + protected List> XmlElementToTcmUriList(XmlElement data) + { + List> res = new List>(); + foreach (XmlNode item in data.SelectNodes("/*/*")) + { + string title = item.Attributes["Title"].Value; + TcmUri id = new TcmUri(item.Attributes["ID"].Value); + res.Add(new KeyValuePair(id, title)); + } + return res; + } + #endregion + + #region Json Data Processing + protected Dictionary MergeData(Dictionary source, Dictionary mergeData) + { + foreach (string key in mergeData.Keys) + { + if (!source.ContainsKey(key)) + { + source.Add(key, mergeData[key]); + } + else + { + Logger.Warning(String.Format("Duplicate key ('{0}') found when merging data. The second value will be skipped.", key)); + } + } + return source; + } + + protected List PublishJsonData(Dictionary> settings, Component relatedComponent, string variantName, StructureGroup sg, bool isArray = false) + { + List files = new List(); + foreach (var key in settings.Keys) + { + files.Add(PublishJsonData(settings[key], relatedComponent, key, variantName+key, sg, isArray)); + } + return files; + } + + protected string PublishJsonData(Dictionary data, Component relatedComponent, string filename, string variantName, StructureGroup sg, bool isArray = false) + { + return PublishJsonData(data.Select(i => String.Format("{0}:{1}", JsonEncode(i.Key), JsonEncode(i.Value))).ToList(), relatedComponent, filename, variantName, sg, isArray); + } + + protected string PublishJsonData(List settings, Component relatedComponent, string filename, string variantName, StructureGroup sg, bool isArray = false) + { + if (settings.Count > 0) + { + string json; + if (isArray) + { + json = String.Format("[{0}]", String.Join(",\n", settings)); + } + else + { + json = String.Format("{{{0}}}", String.Join(",\n", settings)); + } + return PublishJson(json, relatedComponent, sg, filename, variantName); + } + return null; + } + + protected string PublishBootstrapJson(List filesCreated, Component relatedComponent, StructureGroup sg, string variantName = null, List additionalData = null) + { + string extras = additionalData != null && additionalData.Count > 0 ? String.Join(",", additionalData) + "," : ""; + return PublishJson(String.Format("{{{0}\"files\":[{1}]}}", extras, String.Join(",", filesCreated.Where(i=>!String.IsNullOrEmpty(i)).ToList())), relatedComponent, sg, BootstrapFilename, variantName + "bootstrap"); + } + + protected string PublishJson(string json, Component relatedComponent, StructureGroup sg, string filename, string variantName) + { + Item jsonItem = Package.CreateStringItem(ContentType.Text, json); + var binary = Engine.PublishingContext.RenderedItem.AddBinary(jsonItem.GetAsStream(), filename + JsonExtension, sg, variantName, relatedComponent, JsonMimetype); + Package.PushItem(binary.Url, jsonItem); + return JsonEncode(binary.Url); + } + + protected Dictionary ReadComponentData(Component comp) + { + var settings = new Dictionary(); + if (comp.Content!=null) + { + var fields = new ItemFields(comp.Content, comp.Schema); + var configFields = fields.GetEmbeddedFields("settings"); + if (configFields.Any()) + { + //either schema is a generic multival embedded name/value + foreach (var setting in configFields) + { + var key = setting.GetTextValue("name"); + if (!String.IsNullOrEmpty(key) && !settings.ContainsKey(key)) + { + settings.Add(key, setting.GetTextValue("value")); + } + else + { + Logger.Warning(String.Format("Duplicate key found: '{0}' when processing component {1}", key, comp.Id)); + } + } + } + else + { + //... or its a custom schema with individual fields + foreach (var field in fields) + { + //TODO - do we need to be smarter about date/number type fields? + var key = field.Name; + settings.Add(key, fields.GetSingleFieldValue(key)); + } + } + } + return settings; + } + + protected string JsonEncode(object json) + { + var serializer = new JavaScriptSerializer(); + return serializer.Serialize(json); + } + + #endregion + + #region Module Data Processing + + protected StructureGroup GetSystemStructureGroup(string subStructureGroupTitle=null) + { + var webdavUrl = String.Format("{0}/_System{1}", GetPublication().RootStructureGroup.WebDavUrl, subStructureGroupTitle==null ? "" : "/" + subStructureGroupTitle); + var sg = Engine.GetObject(webdavUrl) as StructureGroup; + if (sg == null) + { + throw new Exception(String.Format("Cannot find structure group with webdav URL: {0}", webdavUrl)); + } + return sg; + } + + protected Dictionary GetActiveModules(Component coreConfigComponent = null) + { + Schema moduleConfigSchema = coreConfigComponent != null ? coreConfigComponent.Schema : GetModuleConfigSchema(); + var results = new Dictionary(); + foreach (var item in GetUsingItems(moduleConfigSchema, ItemType.Component)) + { + try + { + var comp = (Component)Engine.GetObject(Engine.LocalizeUri(item.Key)); + var fields = new ItemFields(comp.Content, comp.Schema); + var moduleName = GetModuleNameFromConfig(comp).ToLower(); + if (fields.GetTextValue("isActive").ToLower() == "yes" && !results.ContainsKey(moduleName)) + { + results.Add(moduleName, comp); + } + } + catch (Exception) + { + //Do nothing, this module is not available in this publication + } + } + return results; + } + + private Schema GetModuleConfigSchema() + { + var pub = this.GetPublication(); + var filter = new RepositoryItemsFilter(pub.Session) + { + ItemTypes = new List { ItemType.Schema }, + Recursive = true + }; + foreach (var item in XmlElementToTcmUriList(GetPublication().GetListItems(filter))) + { + if (item.Value == "Module Configuration") + { + return (Schema)pub.Session.GetObject(item.Key); + } + } + throw new Exception("Cannot find Schema named \"Module Configuration\"- please check that this has not been renamed."); + } + + protected string GetModuleNameFromConfig(Component configComponent) + { + //Module config components are always found in /Modules/{Name}/System/, so the module name is defined to be the name of the folder 2 levels up. + return configComponent.OrganizationalItem.OrganizationalItem.Title.ToLower(); + } + + protected string GetModulesRoot(Component configComponent) + { + //Module config components are always found in /Modules/{Name}/System/, so the module root is defined as the folder 3 levels up. + return configComponent.OrganizationalItem.OrganizationalItem.OrganizationalItem.WebDavUrl; + } + + protected string GetModuleNameFromItem(RepositoryLocalObject item, string moduleRoot) + { + //The module name is the name of the folder within the first level of the module root folder + //in which the item lives + var fullItemWebdavUrl = item.WebDavUrl; + if (fullItemWebdavUrl.StartsWith(moduleRoot)) + { + Logger.Debug(fullItemWebdavUrl + ":" + moduleRoot); + var res = fullItemWebdavUrl.Substring(moduleRoot.Length + 1); + var pos = res.IndexOf("/", StringComparison.Ordinal); + Logger.Debug(res); + return res.Substring(0, pos).ToLower(); + } + return null; + } + + protected static string GetRegionFromTemplate(ComponentTemplate template) + { + var match = Regex.Match(template.Title, @".*?\[(.*?)\]"); + if (match.Success) + { + return match.Groups[1].Value; + } + //default region name + return "Main"; + } + #endregion + + } +} diff --git a/content-management/Sdl.Web.Templating.MediaManager/Properties/AssemblyInfo.cs b/content-management/Sdl.Web.Templating.MediaManager/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c283f95 --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Sdl.Web.Templating.MediaManager")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Sdl.Web.Templating.MediaManager")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("01e14279-60d9-4f3e-bc14-83d563cac2b4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/content-management/Sdl.Web.Templating.MediaManager/Sdl.Web.Templating.MediaManager.csproj b/content-management/Sdl.Web.Templating.MediaManager/Sdl.Web.Templating.MediaManager.csproj new file mode 100644 index 0000000..fd56d1f --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Sdl.Web.Templating.MediaManager.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {4B9444A9-D8C4-43FD-ABEA-02BFA7123B8C} + Library + Properties + Sdl.Web.Templating.MediaManager + Sdl.Web.Templating.MediaManager + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + ..\_references\2013-sp1\Tridion.Common.dll + + + ..\_references\2013-sp1\Tridion.ContentManager.dll + + + ..\_references\2013-sp1\Tridion.ContentManager.Common.dll + + + ..\_references\2013-sp1\Tridion.ContentManager.Publishing.dll + + + ..\_references\2013-sp1\Tridion.ContentManager.Templating.dll + + + ..\_references\ecl\Tridion.ExternalContentLibrary.Templating.V2.dll + + + ..\_references\2013-sp1\Tridion.Logging.dll + + + + + + + + + + + + + \ No newline at end of file diff --git a/content-management/Sdl.Web.Templating.MediaManager/Templates/ResolveMediaManagerReferences.cs b/content-management/Sdl.Web.Templating.MediaManager/Templates/ResolveMediaManagerReferences.cs new file mode 100644 index 0000000..cddf9fd --- /dev/null +++ b/content-management/Sdl.Web.Templating.MediaManager/Templates/ResolveMediaManagerReferences.cs @@ -0,0 +1,90 @@ +using Sdl.Web.Tridion.Common; +using System; +using System.Linq; +using System.Xml; +using Tridion.ContentManager.ContentManagement; +using Tridion.ContentManager.Templating; +using Tridion.ContentManager.Templating.Assembly; +using Tridion.ExternalContentLibrary.Templating.V2; + +namespace Sdl.Web.Tridion.Templates.MediaManager +{ + /// + /// Resolves Media Manager references from the package output. + /// Update Url tags with the Media Manager Url so these can + /// be resolved correctly by the web application. + /// + /// + /// Should be placed after the "Publish binaries for component" TBB + /// + [TcmTemplateTitle("Resolve Media Manager References")] + public class ResolveMediaManagerReferences : TemplateBase + { + private ExternalContentLibraryFunctionSource _eclFunctions; + + protected void Init(Engine engine, Package package) + { + Initialize(engine, package); + _eclFunctions = new ExternalContentLibraryFunctionSource(); + _eclFunctions.Initialize(engine, package); + } + + public override void Transform(Engine engine, Package package) + { + try + { + Init(engine, package); + + Component comp = GetComponent(); + if (IsPageTemplate() || comp == null) + { + Logger.Error("No Component found (is this a Page Template?)"); + return; + } + Item outputItem = package.GetByName(Package.OutputName); + if (outputItem == null) + { + Logger.Error("No Output package item found (is this TBB placed at the end?)"); + return; + } + + XmlDocument doc = new XmlDocument(); + string output = outputItem.GetAsString(); + doc.LoadXml(output); + var mediaManagerComponents = doc.SelectNodes("//Field[@FieldType='MultiMediaLink']/LinkedComponentValues/Component/Multimedia/MimeType['application/externalcontentlibrary']/../.."); + foreach (XmlElement mediaManagerComponent in mediaManagerComponents) + { + mediaManagerComponent.InnerXml = ResolveMediaManagerReference(mediaManagerComponent.InnerXml); + } + package.Remove(outputItem); + package.PushItem(Package.OutputName, package.CreateXmlDocumentItem(ContentType.Xml, doc)); + } + catch (Exception e) + { + Logger.Error(e.Message, e); + } + } + + private string ResolveMediaManagerReference(string input) + { + XmlDocument doc = new XmlDocument(); + var nsmgr = new XmlNamespaceManager(doc.NameTable); + doc.LoadXml(String.Format("{0}", input)); + + string id = doc.SelectSingleNode("/Component/Id", nsmgr).IfNotNull(i => i.InnerText); + if (!String.IsNullOrEmpty(id)) + { + XmlNode urlNode = doc.SelectSingleNode("/Component/Multimedia/Url", nsmgr); + if (urlNode != null) + { + if (_eclFunctions.IsExternalContentLibraryComponent(id)) + { + urlNode.InnerText = _eclFunctions.GetExternalContentLibraryDirectLink(id); + } + } + } + + return doc.DocumentElement.InnerXml; + } + } +} diff --git a/content-management/_references/ecl/missing_files.md b/content-management/_references/ecl/missing_files.md new file mode 100644 index 0000000..bb01e68 --- /dev/null +++ b/content-management/_references/ecl/missing_files.md @@ -0,0 +1,6 @@ +Since the CM libraries cannot be distributed without a signed license agreement, they have been removed from this repository. + +The following file is required in this directory for a build: + +- Tridion.ExternalContentLibrary.Templating.V2.dll + diff --git a/modules/MediaManager-Module-ContentPort.zip b/modules/MediaManager-Module-ContentPort.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a6dcdd58f22338b234ed34d6937edb4da5a207f GIT binary patch literal 21978 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 Date: Fri, 31 Jul 2015 11:40:15 -0700 Subject: [PATCH 5/5] Added Media Manager module installation package --- ...ontentPort.zip => MediaManager-Module.zip} | Bin 21978 -> 22613 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/{MediaManager-Module-ContentPort.zip => MediaManager-Module.zip} (90%) diff --git a/modules/MediaManager-Module-ContentPort.zip b/modules/MediaManager-Module.zip similarity index 90% rename from modules/MediaManager-Module-ContentPort.zip rename to modules/MediaManager-Module.zip index 5a6dcdd58f22338b234ed34d6937edb4da5a207f..169b522ebdf0e39a4a27b94c0272d09d55c886fd 100644 GIT binary patch delta 863 zcmcb$n(^ueMx_96W)=|!5HN}Q?*?W-2^JuooLj6vL5f2K%G@Y7krU3DC?_^qHaH@wG$-!_o`oCLlnjCv3BLhPg69WS$P*sqtql>SrUT#Y6MBnT~ z4gziGx&E~)#@%WTTz|Afe(3|nfIkbS%iT#)x7u)%clYX0um9ER(#)z~CTy^le{cN$ zjX=;86$R0#ITxHVpSUWD96OY^|L%cODgQ?=7udhxZ{Kk79C!WF7WKu>9m&~?ZL{uk zU3n-dBjdewW_hx0{9Z;aZM(yU`ghY0*v)dwbe27+7II{JkZhyL_8-P&f~EEtDzD_eiao$i{uQ2I;tn)MYaXWiDXc&9MK zZC3U`$ZT`3T+OI~(|Kb7Oj7;_nxZ)WUy&xa}p}?`u#h?J@F)~OnxSN^l?>&8i5k^=4 zuT=%{5vFo5y!ZR<=FC$5DjXQcQd&g}3@Shf0U(2vfEbjRCdw&HJ{lsz$G5^5Y$Sx9 z{5(WX3{}H!pg9l?pj0Nur*j^n0YXoX43!g;MzLY3AVkCD&QR@oi*FFsFdEsVn28eE up-V%Mk|=T@M4o>6eIL-%{w~(mAkV|-0B=?{kUv?0kPDc`O~V)%fD`~w${%C^ delta 227 zcmcb*f$`RAMwS3?W|qkl!^Bt=7#J8P?+sJfm{Q0I=4`&p`G}oG94Nf`xZne)$-QAx zLJABjKt2d4fJv|x6F$a@8hy*a3yctY^8YY7G?i01fhzBEDohRz7qK&b3sDWD1H2iT z>={IW268a;`2BXXOlGM#?!>_0=)u4sj$&c8AkdhV;o539i)^w%7Q*NNZ&o&tgP4KP Mmyv;?JOso80G^dIy8r+H