Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Santanaf 6 posts 26 karma points
    Jan 13, 2012 @ 21:04
    Santanaf
    0

    PDF Document Handling as Content Elements, not Media

    I'm in the process of implementing Umbraco 4.7.1 for a content heavy website (~7,000-10,000 static pages). Due to the nature of the website, a large number of "pages" are PDFs rathern than HTML (gotta love it). As a result, we'd like to allow content owners to view their PDF content along side of their HTML content in the content tree. We'd also like to ensure that PDF content urls maintain a friendly url structure similar to the content it sits near in the tree, rather than managing them within the Media section.

    Essentially if we have ContentElement1 and PdfElement1, both children of ParentElement1, the urls should be:

    http://domain.com/ParentElement1/
    http://domain.com/ParentElement1/ContentElement1/
    http://domain.com/ParentElement1/PdfElement1.pdf

    To accomplish this I've created a PDF document type that includes an Upload property that will house the actual file. I've also added a Template that will act as a conduit for requests, grabbing the current node's property information and determining the file to pass to the user. If you navigate to this url now, a Response.TransmitFile() occurs along with proper header information to directly stream the PDF to the user based on their request. The final step was to then configure the URLRewrite to handle ~/(.*).pdf and hand it off to ~/$1/.

    Everything is working as I want it to at this point, but I'm at my final stage and would greatly appreciate a little help.

    Since we've set umbracoUseDirectoryUrls to true, it seems there is no way to override the link to document to force the .pdf extension I'd prefer. I've attempted umbracoUrlAlias, but that doesn't seem to work. Any ideas on how I may be able to handle this aspect of what I'm trying to accomplish?

    Any help is greatly appreciated.

  • Tim 1193 posts 2675 karma points MVP 4x c-trib
    Jan 23, 2012 @ 16:51
    Tim
    0

    Hiya,

    What you've done seems fine, but I don't think there's an easy way to get umbraco to append the PDF file extension to the end of the URLs, as by default you're limited to either extensionless /pagename or .aspx /pagename.aspx.

    You could make your own version of NiceUrl to render out PDF document types with the .pdf on the end of the URL and use that in your macros instead of the umbraco.library.NiceUrl one, but you'd have problems with any links to the PDFs that are linked to in the Richtext editor, as the links are replaced somewhere that you can't override so those links would not get the .PDF extension added to them.

  • Santanaf 6 posts 26 karma points
    Jan 24, 2012 @ 23:55
    Santanaf
    0

    Tim,

    Thanks for the response. I actually think I have a pretty good and workable solution right now. I'm new around here, and not sure how the contribution of features back into the code works, but I might see if it is worth submitting. Here's what I did in case anyone else would like static content files (like PDFs) to show up in the content tree along with other content elements:

    1. Update Url Rewrite

     

    <add name="StaticFileHanlder" 
    virtualUrl="~/(.*).(pdf|doc|xls|txt|xml)"
    rewriteUrlParameter="ExcludeFromClientQueryString"
    destinationUrl="~/$1/"
    ignoreCase="true" />

     

    2. Create a new Template (I called ours  StaticDocument) and set Inherits="Core.Display.Umbraco.DocumentStream"
     
    Set the various requirement content placeholders, etc, that you need

    3. Create a new set of DocumentTypes, one for each static file type you'd like to accomodate. (You could do just 1 DocumentType, but I did multiple to allow different icons for each type in the tree). Make this DocumentType use the default template of StaticDocument. For this first example, call it PDFDocument (or at least makt this the alias).

    4. In the PDFDocument properties, add the following two:
    File (staticFile), Type: Upload 
    File Extension (umbracoFileExtension), Type: Label

    5. Create a .cs file that you will place in the App_Code folder with the following content

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web.UI;
    using System.Web;
    using System.IO;
    using umbraco.NodeFactory;
    using umbraco.interfaces;

    namespace Core.Display.Umbraco {

    public class DocumentStream : MasterPageTemplate {
    public DocumentStream(){
    this.Load += new EventHandler(DocumentStreamLoad);
    }

    private void DocumentStreamLoad(object sender, EventArgs e){
    Node currentNode = ((UmbracoBasePage) this.Page).CurrentNode;
    string extension = currentNode.Properties["umbracoFileExtension"].Value;
    if (Request.RawUrl.Split('?')[0].Split('#')[0].ToLower().EndsWith(extension.ToLower())){
    //This is a call to a GetContentType Method we have, 
    //see this URL for a non proprietary solution 
    //http://stackoverflow.com/questions/58510/using-net-how-can-you-find-the-mime-type-of-a-file-based-on-the-file-signature
    Response.ContentType = ModuleConfig.GetContentType(extension);
    Response.TransmitFile(Server.MapPath(currentNode.Properties["staticFile"].Value));
    Response.End();
    }
    Response.Write("Need to do some work to throw an actual 404 but wanted to get this solution up.");
    Response.End();
    }
    }
    }

    6. Make adjustments to the umbraco.presentation.library.cs file's NiceUrlJuno, NiceUrlFetch, and appendUrlExtension methods with the following

    internal static string niceUrlJuno(int nodeId, int startNodeDepth, string currentDomain, bool forceDomain, out string nodeExtension)
            {
                string parentUrl = String.Empty;
    nodeExtension = String.Empty;
                XmlElement node = UmbracoContext.Current.GetXml().GetElementById(nodeId.ToString());
     
                if (node == null)
                {
                    ArgumentException arEx =
                        new ArgumentException(
                            string.Format(
                                "Couldn't find any page with the nodeId = {0}. This is most likely caused by the page isn't published!",
                                nodeId), "nodeId");
                    Log.Add(LogTypes.Error, nodeId, arEx.Message);
                    throw arEx;
                }
                if (node.ParentNode.Name.ToLower() != "root" || UmbracoSettings.UseDomainPrefixes || forceDomain)
                {
                    if (UmbracoSettings.UseDomainPrefixes || forceDomain)
                    {
                        Domain[] domains =
                            Domain.GetDomainsById(nodeId);
                        // when there's a domain on a url we'll just return the domain rather than the parent path
                        if (domains.Length > 0)
                        {
                            return GetDomainIfExists(currentDomain, nodeId);
                        }
                    }
     
                    if (parentUrl == String.Empty && (int.Parse(node.Attributes.GetNamedItem("level").Value) > startNodeDepth || UmbracoSettings.UseDomainPrefixes ||
    forceDomain))
                    {
                        if (node.ParentNode.Name != "root")
                        {
                            parentUrl = niceUrlJuno(int.Parse(node.ParentNode.Attributes.GetNamedItem("id").Value), startNodeDepth, currentDomain, forceDomain, out
    nodeExtension);
                        }
                    }
                }
                 // only return the current node url if we're at the startnodedepth or higher
    if (int.Parse(node.Attributes.GetNamedItem("level").Value) >= startNodeDepth) {
    if (node.SelectSingleNode("umbracoFileExtension") != null &&
    !string.IsNullOrEmpty(node.SelectSingleNode("umbracoFileExtension").InnerText))
    nodeExtension = node.SelectSingleNode("umbracoFileExtension").InnerText;
    else
    nodeExtension = ".aspx";
    return parentUrl + "/" + node.Attributes.GetNamedItem("urlName").Value;
    } else if (node.PreviousSibling != null)
    return "/" + node.Attributes.GetNamedItem("urlName").Value;
    else
    return "/";
            }
    internal static string NiceUrlFetch(int nodeID, int startNodeDepth, bool forceDomain)
            {
                bool directoryUrls = GlobalSettings.UseDirectoryUrls;
                string baseUrl = SystemDirectories.Root; // SystemDirectories.Umbraco;
    string nodeExtension;
    string junoUrl = niceUrlJuno(nodeID, startNodeDepth, HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToLower(), forceDomain, out nodeExtension);
    return appendUrlExtension(baseUrl, directoryUrls, junoUrl, nodeExtension);
     
            }
     
    private static string appendUrlExtension(string baseUrl, bool directoryUrls, string tempUrl, string extensionToAppend)
            {
                if (!directoryUrls)
                {
                    // append .aspx extension if the url includes other than just the domain name
                    if (!String.IsNullOrEmpty(tempUrl) && tempUrl != "/" &&
                        (!tempUrl.StartsWith("http://") || tempUrl.LastIndexOf("/") > 7))
    tempUrl = baseUrl + tempUrl + extensionToAppend;
                }
                else
                {
                    tempUrl = baseUrl + tempUrl;
    if (extensionToAppend != ".aspx") {
    tempUrl += extensionToAppend;
    } else if (tempUrl != "/" && UmbracoSettings.AddTrailingSlash) {
    tempUrl += "/";
    }
                }
                return tempUrl;
            }

    7. Finally, add the default value of file extension to the content element on save based on the document type that is selected. Create a DefaultValues.cs file and drop it in your App_Code directory that looks like this.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using umbraco.cms.businesslogic.web;
    using umbraco.cms.businesslogic;
    using umbraco.BusinessLogic;

    namespace Core.Display.Umbraco {
    public class DefaultValues : umbraco.BusinessLogic.ApplicationBase {
    public DefaultValues() {
    Document.BeforeSave += new Document.SaveEventHandler(Document_BeforeSave);
    }
    void Document_BeforeSave(Document sender, SaveEventArgs e) {

    switch(sender.ContentType.Alias){
    case "PDFDocument":
    try{
    sender.getProperty("umbracoFileExtension").Value = ".pdf";
    }catch{}
    break;
    case "WordDocument":
    try {
    sender.getProperty("umbracoFileExtension").Value = ".doc";
    } catch { }
    break;
    case "ExcelDocument":
    try {
    sender.getProperty("umbracoFileExtension").Value = ".xls";
    } catch { }
    break;
    case "TextDocument":
    try {
    sender.getProperty("umbracoFileExtension").Value = ".txt";
    } catch { }
    break;
    case "ZipDocument":
    try {
    sender.getProperty("umbracoFileExtension").Value = ".zip";
    } catch { }
    break;

    }
    }
    }

    8. Create your PDF (or whatever) and publish it. You'll notice the URL is what you want it to be, LocalLink resolves to the correct location, and all is well in the world.

    There may be a 1000% easier solution that what I've done, but forgive me, I'm an Umbraco n00b, and this works really well for our purposes. The next step will be to find a control, or create one on our own, that allows upload only based on file extension. I could also see default values of labels being easier than this, perhaps with a hidden label default value data type or something. Hope this is helpful to someone.

     

  • Santanaf 6 posts 26 karma points
    Jan 24, 2012 @ 23:55
    Santanaf
    0

    Gah, I took so much time on proper formatting and tabbing of my reply...oh well.

Please Sign in or register to post replies

Write your reply to:

Draft