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/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); 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.zip b/modules/MediaManager-Module.zip new file mode 100644 index 0000000..169b522 Binary files /dev/null and b/modules/MediaManager-Module.zip differ 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; } 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); } } }