Example #1
0
        /// <summary>
        /// Gets the relative path of the template to use with the current file taking into account all the possible parameters/fields that control this setting
        /// </summary>
        /// <returns></returns>
        private static string GetCurrentTemplateFile(MarkdownFile md)
        {
            //Get the template name that is going to be used (Front Matter or configuration), if any.
            string templateName = md.TemplateName;

            if (string.IsNullOrEmpty(templateName) || templateName.ToLowerInvariant() == "none")
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            if (templateName.ToLowerInvariant() == "raw")
            {
                return("raw");   //Use raw contents, without any wrapping HTML tags
            }
            //The name (or sub-path) for the layout file (.html normaly) to be used
            string layoutName = md.Layout;

            if (string.IsNullOrEmpty(layoutName))
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            //If both the template folder and the layout are established, then get the base folder for the templates
            //This base path for the templates parameter is only available through Web.config. NOT in the file Front Matter (we're skipping the file in the following call)
            string basePath = FieldValuesHelper.GetFieldValue("TemplatesBasePath", null, "~/Templates/");

            return(VirtualPathUtility.AppendTrailingSlash(basePath) + VirtualPathUtility.AppendTrailingSlash(templateName) + layoutName);
        }
Example #2
0
        //Process the requests
        public void ProcessRequest(HttpContext ctx)
        {
            try
            {
                //Try to process the markdown file
                string       filePath = ctx.Server.MapPath(ctx.Request.FilePath);
                MarkdownFile mdFile   = new MarkdownFile(filePath);

                //If the feature is enabled and the user requests the original file, send the original file
                if (!string.IsNullOrEmpty(ctx.Request.QueryString["download"]))
                {
                    if (Common.GetFieldValue("allowDownloading") == "1")
                    {
                        ctx.Response.ContentType = "text/markdown; charset=UTF-8";
                        ctx.Response.AppendHeader("content-disposition", "attachment; filename=" + mdFile.FileName);
                        ctx.Response.Write(mdFile.Content);
                    }
                    else
                    {
                        throw new SecurityException("Download of markdown not allowed. Change configuration.");
                    }
                }
                else
                {
                    //Check if the File is published
                    if (mdFile.IsPublished)
                    {
                        //Check if is a 404 page
                        if (mdFile.HttpStatusCode != 200)
                        {
                            ctx.Response.StatusCode = mdFile.HttpStatusCode;
                        }

                        //Send the rendered HTML for the file
                        ctx.Response.ContentType = "text/html";
                        ctx.Response.Write(mdFile.HTML);
                    }
                    else
                    {
                        throw new FileNotFoundException();
                    }
                }
            }
            catch (SecurityException)
            {
                //Access to file not allowed
                ctx.Response.StatusDescription = "Forbidden";
                ctx.Response.StatusCode        = 403;
            }
            catch (FileNotFoundException)
            {
                //Normally IIS will take care, but you can disconnect it
                ctx.Response.StatusDescription = "File not found";
                ctx.Response.StatusCode        = 404;
            }
            catch (Exception)
            {
                throw;
            }
        }
Example #3
0
        /// <summary>
        /// Renders the HTML from the markdown using the templates and parameters specified in web.config
        /// and processing the templates
        /// </summary>
        /// <param name="md">The markdown file information</param>
        /// <param name="raw">If true will force the raw template: only te content, without any extra HTML.
        /// This is useful for getting the pure, fully processed content of the file, without any extra HTML</param>
        /// <returns>The final HTML to return to the client</returns>
        public static string RenderMarkdownFile(MarkdownFile md)
        {
            //Get current template and layout
            HttpContext ctx = HttpContext.Current;

            string template     = DEFAULT_TEMPLATE;           //The default template for the final HTML
            string templateFile = GetCurrentTemplateFile(md); //Get the curent template layout path

            if (!string.IsNullOrEmpty(templateFile))
            {
                //If the specified template is "raw" then just return the raw HTML without any wrapping HTML code
                //(no html, head or body tags). Useful to return special pages with raw content.
                if (templateFile == "raw")
                {
                    template = "{{content}}";
                }
                else
                {
                    List <string> templateDependencies = new List <string>();
                    try
                    {
                        template = ReadTemplate(templateFile, ctx, templateDependencies);    //Read, transform and cache template
                        //Add template file's dependences as dependences for the Markdown file cache too
                        md.AddFileDependencies(templateDependencies);
                    }
                    catch (FileNotFoundException)
                    {
                        throw new Exception("Current template file is not accessible! Check the TemplateName and Layout properties for the site or the current file");
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
            }

            //Check if there're fragments in the layout and process them
            template = InjectFragments(template, md, ctx);

            //Process the template with DotLiquid for this file (the {{content}} placeholder is resolved in the MDFieldsResolver
            Template parser      = Template.Parse(template);
            string   tempContent = parser.Render(md.FieldsResolver);  //The file contents get rendered into the template by the {{content}} placeholder

            CheckParserForSpecialErrors(parser.Errors);

            //Transform virtual paths
            tempContent = WebHelper.TransformVirtualPaths(tempContent);

            //HACK: remove possible delimiters to prevent the processing of HTML contents inside Markdown files (see MDFieldsResolver.cs)
            //Normal delimiters
            tempContent = tempContent.Replace(MDFieldsResolver.HTML_NO_PROCESSING_DELIMITER_BEGIN, string.Empty).
                          Replace(MDFieldsResolver.HTML_NO_PROCESSING_DELIMITER_END, string.Empty);
            //Delimiters inside blocks (shouldn't be located there, but...) that get HTMLEncoded for that reason
            tempContent = tempContent.Replace(WebUtility.HtmlEncode(MDFieldsResolver.HTML_NO_PROCESSING_DELIMITER_BEGIN), string.Empty)
                          .Replace(WebUtility.HtmlEncode(MDFieldsResolver.HTML_NO_PROCESSING_DELIMITER_END), string.Empty);

            //Return the final content
            return(tempContent);
        }
Example #4
0
        public static string RenderLiquidTags(string rawContent, MarkdownFile contextFile)
        {
            //Process the content tags (if any) with DotLiquid
            Template parser = Template.Parse(rawContent);
            string   res    = parser.Render(contextFile.FieldsResolver);

            //Check if a custom error has been raised
            CheckParserForSpecialErrors(parser.Errors);
            return(res);
        }
Example #5
0
        /// <summary>
        /// Returns the value, if any, for a specified field name. It takes the value from the FrontMatter only.
        /// If it's not present in the Front Matter, it returns the default value.
        /// </summary>
        /// <param name="name">The name of the field to retrieve</param>
        /// <param name="md">An optional Markdown file to check in its front matter</param>
        /// <param name="defValue">The default value to return if it's not present</param>
        /// <returns>Can return different types: null, boolean, string, array or date</returns>
        public static object GetFieldObjectFromFM(string name, MarkdownFile md, object defValue = null)
        {
            string res = GetFieldValueFromFM(name, md, "");

            if (res == "")
            {
                return(defValue);
            }

            return(TypesHelper.TryToGuessAndConvertToTypeFromString(res));
        }
Example #6
0
        //Process the requests
        public void ProcessRequest(HttpContext ctx)
        {
            try
            {
                //Try to process the markdown file
                string       filePath = ctx.Server.MapPath(ctx.Request.FilePath);
                MarkdownFile mdFile   = new MarkdownFile(filePath);

                //Check if the File is published
                if (mdFile.IsPublished)
                {
                    //Check if is a special status code page (404, etc)
                    if (mdFile.HttpStatusCode != 200)
                    {
                        ctx.Response.StatusCode = mdFile.HttpStatusCode;
                    }

                    //Send the rendered content for the file
                    ctx.Response.ContentType = mdFile.MimeType; //text/html by default
                    if (ctx.Request.Params["raw"] == "true")
                    {
                        ctx.Response.Write(mdFile.Content);
                    }
                    else
                    {
                        ctx.Response.Write(mdFile.FinalHTML);
                    }
                }
                else
                {
                    throw new FileNotFoundException();
                }
            }
            catch (SecurityException)
            {
                //Access to file not allowed
                ctx.Response.StatusDescription = "Forbidden";
                ctx.Response.StatusCode        = 403;
            }
            catch (FileNotFoundException)
            {
                //Normally IIS will take care, but you can disconnect it
                ctx.Response.StatusDescription = "File not found";
                ctx.Response.StatusCode        = 404;
            }
            catch (Exception)
            {
                throw;
            }
        }
Example #7
0
        //Takes care of custom fields such as Front Matter Properties and custom default values in web.config
        private static string ProcessCustomFields(string template, MarkdownFile md, HttpContext ctx)
        {
            string[] names = TemplatingHelper.GetAllPlaceHolderNames(template);
            foreach (string name in names)
            {
                //Get current value for the field, from Front Matter or web.config
                string fldVal = Common.GetFieldValue(name, md);

                /*
                 * There are two types of fields:
                 * - Value fields: {name} -> Get a value from the properties of the file or from web.config -> Simply replace them
                 * - File processing fields (FPF), ending in .md or .mdh. ej: {{myfile.md}} -> The file is read and it's contents transformed into HTML take the place of the placeholder
                 *   Useful for menus, and other independet parts in custom templates and parts of the same page.
                 */
                if (fldVal.EndsWith(".md") || fldVal.EndsWith(MarkdownFile.HTML_EXT))
                {
                    try
                    {
                        string       fpfPath = ctx.Server.MapPath(fldVal); //The File-Processing Field path
                        MarkdownFile mdFld   = new MarkdownFile(fpfPath);
                        fldVal = mdFld.RawHTML;                            //Use the raw HTML, not the processed HTML (this last one includes the template too)
                        //Add the processed file to the dependencies of the currently processed content file, so that the file is invalidated when the FPF changes (if caching is enabled)
                        md.Dependencies.Add(fpfPath);
                    }
                    catch (SecurityException)
                    {
                        fldVal = String.Format("Can't access file for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFIX);
                    }
                    catch (FileNotFoundException)
                    {
                        fldVal = String.Format("File not found for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFIX);
                    }
                    catch (Exception ex)
                    {
                        fldVal = String.Format("Error loading {0}: {1}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFIX, ex.Message);   //This should only happen while testing, never in production, so I send the exception's message
                    }
                }
                else if (fldVal.StartsWith("~/"))    //If its a virtual path to a static file (for example a path to a CSS or JS file)
                {
                    //Convert relative path to relative URL from the root (changes the "~/" for the root path
                    //of the application. Needed if the current handler is running as a virtual app in IIS)
                    fldVal = VirtualPathUtility.ToAbsolute(fldVal);
                    //There's no need to transform any other virtual path because this is done (and cached) on every file the first time is retrieved and transformed
                }
                template = TemplatingHelper.ReplacePlaceHolder(template, name, fldVal);
            }

            return(template);
        }
Example #8
0
        public const string WEB_CONFIG_PARAM_PREFIX = "MIIS:"; //THe prefix to use to search for parameters in web.config

        /// <summary>
        /// Returns the value, if any, for a specified field name. It takes the value from the FrontMatter first, and if it's not there, tries to read it from the current Web.config.
        /// In web.config it first tries to read them prefixed with "MIIS_" to prevent collision with other products, and then without the prefix.
        /// If it's not present neither in the Front Matter nor the Web.config, returns the default value.
        /// </summary>
        /// <param name="name">The name of the field to retrieve</param>
        /// <param name="md">An optional Markdown file to check in its front matter</param>
        /// <param name="defValue">The default value to return if it's not present</param>
        /// <returns></returns>
        public static string GetFieldValue(string name, MarkdownFile md = null, string defValue = "")
        {
            if (md != null) //If there's a file, possibly with a Front Matter
            {
                //Retrieve from the front matter...
                string val = md.FrontMatter[name];
                if (!string.IsNullOrEmpty(val))
                {
                    return(val);
                }
            }

            //Retrieve from Web.config using the app-specific prefix or without it if it's not present
            return(WebHelper.GetParamValue(WEB_CONFIG_PARAM_PREFIX + name, WebHelper.GetParamValue(name, defValue)));
        }
Example #9
0
        //Finds fragment placeholders in the template and insert their contents
        //Fragments are placeholders that start with an asterisk("*") to indicate that instead of finding the value we should
        //look for a file with the same name as the current one and with the indicated suffix in their name.
        //If two (.md and .mdh) files exist with that name, the one with the same extension as the current file gets precedence
        //They allow to insert "fragments" of the same resulting file in the template positions we want.
        //The placeholders that they contain will be later parsed and processed as normal fields in the file
        private static string InjectFragments(string layoutHtml, MarkdownFile md, HttpContext ctx)
        {
            string tempContent = layoutHtml;

            string[] fragments = TemplatingHelper.GetAllPlaceHolderNames(tempContent, phPrefix: FILE_FRAGMENT_PREFIX);
            foreach (string fragmentName in fragments)
            {
                string fragmentContent  = string.Empty;                                                                                                            //Default empty value
                string fragmentFileName = ctx.Server.MapPath(Path.GetFileNameWithoutExtension(md.FileName) + fragmentName.Substring(FILE_FRAGMENT_PREFIX.Length)); //Removing the "*" at the beginning

                //Test if a file the same file extension exists
                if (File.Exists(fragmentFileName + md.FileExt))
                {
                    fragmentFileName += md.FileExt;
                }
                else  //Try with the other file extension (.md or .mdh depending of the current file's extension)
                {
                    fragmentFileName += (md.FileExt == MarkdownFile.MARKDOWN_DEF_EXT) ? MarkdownFile.HTML_EXT : MarkdownFile.MARKDOWN_DEF_EXT;
                }

                //Try to read the file with fragment
                try
                {
                    md.AddFileDependency(fragmentFileName);

                    MarkdownFile mdFld = new MarkdownFile(fragmentFileName);
                    //Render the file in the context of the parent file, no its own
                    fragmentContent = RenderLiquidTags(mdFld.RawContent, md);   //Render tags in raw content (Markdown or HTML)
                    //If it's Markdown, convert to HTML before substitution
                    if (md.FileExt == MarkdownFile.MARKDOWN_DEF_EXT)
                    {
                        fragmentContent = ConvertMarkdown2Html(fragmentContent, md.UseEmoji, md.EnabledMDExtensions);
                    }
                }
                catch
                {
                    //If something is wrong (normally the file does not exist) simply return an empty string
                    //We don't want to force this kind of files to always exist
                    fragmentContent = string.Empty;
                }
                //Replace the placeholder with the value
                tempContent = TemplatingHelper.ReplacePlaceHolder(tempContent, fragmentName, fragmentContent);
            }

            return(tempContent);
        }
Example #10
0
        public const string WEB_CONFIG_PARAM_PREFIX = "MIIS:"; //THe prefix to use to search for parameters in web.config

        public static string GetFieldValueFromFM(string name, MarkdownFile md, string defValue = "")
        {
            if (md != null) //If there's a file, possibly with a Front Matter
            {
                //Retrieve from the front matter...
                string val = md.FrontMatter[name];
                if (!string.IsNullOrEmpty(val))
                {
                    return(val);
                }
                else
                {
                    return(defValue);    //Return defValue if field is not available
                }
            }
            else
            {
                return(defValue);    //Return defValue if there's not MD file to process
            }
        }
Example #11
0
        //Finds fragment placeholders and insert their contents
        private static string ProcessFragments(string template, MarkdownFile md, HttpContext ctx)
        {
            string[] fragments = TemplatingHelper.GetAllPlaceHolderNames(template, phPrefix: FILE_FRAGMENT_PREFIX);
            foreach (string fragmentName in fragments)
            {
                string fragmentContent  = string.Empty;                                                                                                            //Default empty value
                string fragmentFileName = ctx.Server.MapPath(Path.GetFileNameWithoutExtension(md.FileName) + fragmentName.Substring(FILE_FRAGMENT_PREFIX.Length)); //Removing the "*" at the beggining
                //Test if a file the same file extension exists
                if (File.Exists(fragmentFileName + md.FileExt))
                {
                    fragmentFileName += md.FileExt;
                }
                else if (File.Exists(fragmentFileName + ".md")) //Try with .md extension
                {
                    fragmentFileName += ".md";
                }
                else
                {
                    fragmentFileName += ".mdh"; //Try with .mdh
                }
                //Try to read the file with fragment
                try
                {
                    md.Dependencies.Add(fragmentFileName);

                    MarkdownFile mdFld = new MarkdownFile(fragmentFileName);
                    fragmentContent = mdFld.RawHTML;
                }
                catch
                {
                    //If something is wrong (normally the file does not exist) simply return an empty string
                    //We don't want to force this kind of files to always exist
                    fragmentContent = string.Empty;
                }
                //Replace the placeholder with the value
                template = TemplatingHelper.ReplacePlaceHolder(template, fragmentName, fragmentContent);
            }

            return(template);
        }
Example #12
0
        /// <summary>
        /// Gets the relative path of the template to use with the current file taking into account all the possible parameters/fields that control this setting
        /// </summary>
        /// <returns></returns>
        private static string GetCurrentTemplateFile(MarkdownFile md)
        {
            //Get the template name that is going to be used (Front Matter or configuration), if any.
            string templateName = Common.GetFieldValue("TemplateName", md);

            if (string.IsNullOrEmpty(templateName))
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            //The name (or sub-path) for the layout file (.html normaly) to be used
            string layoutName = Common.GetFieldValue("Layout", md);

            if (string.IsNullOrEmpty(layoutName))
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            //If both the template folder and the layout are established, then get the base folder for the templates
            //This base path for the templates parameter is only available through Web.config. NOT in the file Front Matter (we're skipping the file in the following call)
            string basePath = Common.GetFieldValue("TemplatesBasePath", defValue: "~/Templates/");

            return(VirtualPathUtility.AppendTrailingSlash(basePath) + VirtualPathUtility.AppendTrailingSlash(templateName) + layoutName);
        }
Example #13
0
        private static string FILE_FRAGMENT_PREFIX = "*";  //How to identify fragments placeholders in content files
        #endregion

        #region Methods
        /// <summary>
        /// Renders the HTML from the markdown using the templates and parameters specified in web.config
        /// and processing the templates
        /// </summary>
        /// <param name="md">The markdown file information</param>
        /// <returns>The final HTML to return to the client</returns>
        public static string RenderMarkdown(MarkdownFile md)
        {
            HttpContext ctx          = HttpContext.Current;
            string      template     = DEFAULT_TEMPLATE; //The default template for the final HTML
            string      templateFile = GetCurrentTemplateFile(md);

            if (!string.IsNullOrEmpty(templateFile))
            {
                List <string> templateDependencies = new List <string>();
                template = ReadTemplate(templateFile, ctx, templateDependencies);    //Read, transform and cache template
                //Add template file's dependences as dependences for the Markdown file cache too
                md.Dependencies.AddRange(templateDependencies);
            }

            //First process the "content" field with the main HTML content transformed from Markdown
            //This allows to use other fields inside the content itself, not only in the templates
            string finalContent = TemplatingHelper.ReplacePlaceHolder(template, "content", md.RawHTML);

            //Process fragments (other files inserted into the current one or template)
            finalContent = ProcessFragments(finalContent, md, ctx);

            //Process well-known fields one by one
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "title", md.Title);
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "filename", md.FileName);
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "datecreated", md.DateCreated.ToString());
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "datemodified", md.DateLastModified.ToString());
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "isauthenticated", ctx.User.Identity.IsAuthenticated.ToString());
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "authtype", ctx.User.Identity.AuthenticationType);
            finalContent = TemplatingHelper.ReplacePlaceHolder(finalContent, "username", ctx.User.Identity.Name);

            //Process custom fields
            finalContent = ProcessCustomFields(finalContent, md, ctx);

            //Transfrom virtual paths
            finalContent = WebHelper.TransformVirtualPaths(finalContent);

            //Return the transformed file
            return(finalContent);
        }
Example #14
0
        //Registers all custom Front-Matter sources inside the assembly passed as a parameter
        private static void RegisterCustomFMSourcesInAssembly(Assembly assembly)
        {
            //Custom FM sources are obtained from classes in the FMSources namespace that implement the IFMSource interface
            var fmSources = from c in assembly.GetTypes()
                            where c.IsClass && c.Namespace == CUSTOM_FMSOURCES_NAMESPACE && typeof(IFMSource).IsAssignableFrom(c)
                            select c;

            //Register each FMSource globally using its factory method (GetFilterType)
            fmSources.ToList().ForEach(fmSourceClass =>
            {
                IFMSource fms = (IFMSource)Activator.CreateInstance(fmSourceClass);

                //Register possible fields that will define different caches for the file
                if (fms is IQueryStringDependent)
                {
                    MarkdownFile.AddCachingQueryStringFields(
                        (fms as IQueryStringDependent).GetCachingQueryStringFields()
                        );
                }

                FieldValuesHelper.AddFrontMatterSource(fms.SourceName, fms.GetType());
            });
        }
Example #15
0
 //Constructor
 internal MDFieldsResolver(MarkdownFile mdFile)
 {
     _parentFile = mdFile;
     _mdProxy    = new MIISFile(_parentFile);
     _ctx        = HttpContext.Current;
 }
Example #16
0
        //Retrieves the value for the specified field or returns an empty string if it doesn't exist
        protected override object GetValue(string name)
        {
            object res = "";    //Default value (empty string)

            switch (name.ToLowerInvariant())
            {
            //Check well Known fields first
            case INTERNAL_REFERENCE_TO_CURRENT_FILE:
                //This is intended to be used internally only, from custom tags or front-matter custom sources
                res = _mdProxy;
                break;

            case "content":                     //The final HTML content, WITHOUT the template and WITH liquid tags processed
                res = _parentFile.RawFinalHtml; //This is needed to avoid that the Markdown conversion messes up with the liquid tags (loops, conditionals...)
                break;

            case "title":
                res = _parentFile.Title;
                break;

            case "excerpt":
            case "description":
            case "summary":
                res = _parentFile.Excerpt;
                break;

            case "filename":
                res = _parentFile.FileName;
                break;

            case "filenamenoext":
                res = _parentFile.FileNameNoExt;
                break;

            case "fileext":
                res = _parentFile.FileExt;
                break;

            case "dir":
                res = _mdProxy.Dir;
                break;

            case "date":
                res = _parentFile.Date;
                break;

            case "datecreated":
                res = _parentFile.DateCreated;
                break;

            case "datemodified":
                res = _parentFile.DateLastModified;
                break;

            case "isauthenticated":
                res = _ctx.User.Identity.IsAuthenticated;
                break;

            case "authtype":
                res = _ctx.User.Identity.AuthenticationType;
                break;

            case "username":
                res = _ctx.User.Identity.Name;
                break;

            case "domain":
                res = _ctx.Request.Url.Authority;
                break;

            case "baseurl":
                res = $"{_ctx.Request.Url.Scheme}{System.Uri.SchemeDelimiter}{_ctx.Request.Url.Authority}";
                break;

            case "clientip":
                res = WebHelper.GetIPAddress();
                break;

            case "now":
            case "today":
                res = DateTime.Now;
                break;

            case "time":
                res = DateTime.Now.ToString("hh:mm:ss tt");
                break;

            case "url":
                res = _ctx.Request.RawUrl;
                break;

            case "urlnoext":
                res = IOHelper.RemoveFileExtension(_ctx.Request.RawUrl);
                //Files processed by MIIS always have extension on disk
                //res = _ctx.Request.Path.Remove(_ctx.Request.Path.LastIndexOf("."));
                break;

            case "templatename":
                res = _parentFile.TemplateName;
                break;

            case "layout":
                res = _parentFile.Layout;
                break;

            //Custom fields
            default:
                Exception exToBeRaised = null;                                     //Possible exceptions raised by the next code
                //Check if the custom field has already been retrieved before
                bool isCached = InternalFileFieldCache.TryGetValue(name, out res); //If it's cached the value will be saved to res
                if (!isCached)                                                     //If it's not cached (has not been retrieved before) then retrieve it
                {
                    res = string.Empty;                                            //Default value

                    /*
                     * There are 4 types of custom fields:
                     * - Value fields: {{name}} -> Get a value from the Front-Matter or from web.config -> Simply replace them (default assumption)
                     * - File processing fields (FPF), {{name}} whose value ends in .md or .mdh. ej: myfile.md -> if available the file is read and it's contents transformed into HTML take the place of the placeholder
                     *   Useful for menus, and other independent parts in custom templates and parts of the same page.
                     * - Custom Dinamic Field Sources, {{name}} value that start with !! and use a custom class to populate the field with an object. Ej: !!customSource param1 param2
                     * - Querystring or Form fields, retrieved from the current request
                     */

                    //Try to get a typed value for the field
                    var typedValue = FieldValuesHelper.GetFieldObject(name, _parentFile, null);
                    //If we get a string already, then process it
                    if (typedValue is string)
                    {
                        //If it's a string, process it for special fields

                        //////////////////////////////
                        //Simple value fields (default value if present)
                        //////////////////////////////

                        string resAsString = typedValue.ToString();

                        //////////////////////////////
                        //First, Custom Dinamic Field Sources that provide values from external assemblies
                        //////////////////////////////
                        if (resAsString.StartsWith(FRONT_MATTER_SOURCES_PREFIX))
                        {
                            //Get the name of the source and it's params splitting the string (the first element would be the name of the source, and the rest, the parameters, if any
                            string[] srcelements = resAsString.Substring(FRONT_MATTER_SOURCES_PREFIX.Length).Trim().Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
                            if (srcelements.Length > 0)
                            {
                                try
                                {
                                    res = FieldValuesHelper.GetFieldValueFromFMSource(srcelements[0], _mdProxy, srcelements.Skip(1).ToArray());
                                }
                                catch (Exception ex)
                                {
                                    //Throw the exception to be reflected in the
                                    exToBeRaised = ex;
                                }
                            }
                        }
                        //////////////////////////////
                        //Second, File Processing Fields, that inject the content of .md or .mdh processing their inner fields in their own context
                        //This is for compatbility reasons with MIIS v1.x and 2.x
                        //////////////////////////////
                        else if (resAsString.ToLowerInvariant().EndsWith(MarkdownFile.MARKDOWN_DEF_EXT) || resAsString.ToLowerInvariant().EndsWith(MarkdownFile.HTML_EXT))
                        {
                            //This kind of fields can only be processed in the first level of liquid tags processing.
                            //MustProcessSubFiles is false in the second level
                            //If this is a second level (a FPF inside another FPF) will just return an empty string (won't be processed at all)
                            if (_parentFile.MustProcessSubFiles)
                            {
                                try
                                {
                                    string       fpfPath      = _ctx.Server.MapPath(resAsString); //The File-Processing Field path
                                    MarkdownFile insertedFile = new MarkdownFile(fpfPath, false); //No processing of FPF beyond this layer is allowed to prevent circular references

                                    //If the parent file is a Markdown file
                                    if (_parentFile.FileExt == MarkdownFile.MARKDOWN_DEF_EXT)
                                    {
                                        //HACK: Since {{field}} placeholders are processed BEFORE transforming into HTML
                                        //I need to wrap the injected HTML into a special tag to prevent further processing of the resulting HTML when converting
                                        //the main file contents to HTML. The reason is that mixed HTML is very tricky and can lead to unexpected results
                                        //This is a sloppy way to do it, but it's the only way to mark a section of markdown as "not processable"
                                        //See: https://github.com/lunet-io/markdig/blob/master/src/Markdig.Tests/Specs/CommonMark.md#html-blocks
                                        //Later we need to remove these delimiters in a sloppy way too :-(
                                        res = HTML_NO_PROCESSING_DELIMITER_BEGIN +
                                              insertedFile.ComponentHtml +
                                              HTML_NO_PROCESSING_DELIMITER_END;
                                    }
                                    else
                                    {
                                        //If the parent file is already an HTML file, there's no need for the previous hack
                                        res = insertedFile.ComponentHtml;     //Use the final HTML (WITHOUT the template (except in components) and WITH the liquid tags processed in its own context)
                                    }

                                    //Add the processed file to the dependencies of the currently processed content file,
                                    //so that the file is invalidated when the FPF changes (if caching is enabled)
                                    //The FPF is already cached too if caching is enabled
                                    _parentFile.AddFileDependency(fpfPath);
                                }
                                catch (System.Security.SecurityException)
                                {
                                    res = String.Format("Can't access file for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX);
                                }
                                catch (System.IO.FileNotFoundException)
                                {
                                    res = String.Format("File not found for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX);
                                }
                                catch (Exception ex)
                                {
                                    //This should only happen while testing, never in production, so I send the exception's message
                                    res = String.Format("Error loading {0}: {1}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX, ex.Message);
                                }
                            }
                        }
                        //////////////////////////////
                        //Finally, if it's not a custom source, or a FPF, then is a normal raw string value
                        //////////////////////////////
                        else
                        {
                            res = resAsString;
                        }
                    }
                    else
                    {
                        //If we already got a typed value, then return it by default
                        res = typedValue;

                        //Check if it's null. If it is null, means it hasn't been found in he fields for the file or app,
                        //so maybe the last chance is that it's a request param
                        if (typedValue == null)
                        {
                            //////////////////////////////
                            //Try to determine the param value from the querystring or the form values
                            //////////////////////////////
                            if (!string.IsNullOrWhiteSpace(_ctx.Request.Params[name]))
                            {
                                var paramVal = _ctx.Request.Params[name];     //Result (checks qs, form data, cookies and sv, in that order)
                                if (!string.IsNullOrWhiteSpace(paramVal))
                                {
                                    res = paramVal;                     //A value has been found
                                    _parentFile.CachingEnabled = false; //Disable caching if a param is to be used
                                }
                            }
                        }
                    }

                    //Cache the retrieved value
                    InternalFileFieldCache[name] = res;
                    //If there's been an exception, raise it to inform the renderer about it
                    if (exToBeRaised != null)
                    {
                        throw exToBeRaised;
                    }
                }
                //Get out of the switch construction
                break;
            }
            //Return retrieved value
            return(res);
        }
Example #17
0
 public MIISFile(MarkdownFile mdFile)
 {
     _md = mdFile;
 }