Monday 25 January 2010

Problem: Inserting custom media types in the WYSIWYG of Umbraco 4.0.2.1 shows blank.gif instead of the correct Url



The Scenario
We had a custom media type called Image. We did this to store more information about each image that was uploaded.

The Problem
The CMS user wants to insert the image to the WYSIWYG of one of the content nodes but sees blank.gif inserted into the Url field when they click on the custom media type. This does not occur for standard Umbraco files.

Investigation
I used Charles to see what Umbraco was doing when a user clicks. Umbraco makes a call to ~/umbraco/dialogs/imageViewer.aspx to get the url. Let's take a look at the source code...

protected void Page_Load(object sender, System.EventArgs e)
{
 //Response.Write(umbraco.helper.Request("id"));
 //Response.End();
 // Put user code to initialize the page here
 if (Request.QueryString["id"] != null) 
 {
  if (Request.QueryString["id"] != "")  
  {
   //TODO: fix Nasty FAST'N'CANELINE HACK. ..
   int MediaId = int.Parse(Request.QueryString["id"]);
   
   image.Controls.Clear();
   int fileWidth = 0;
   int fileHeight = 0;
   string fileName = "/blank.gif";
   string altText = "";

   try 
   {
    cms.businesslogic.media.Media m = new cms.businesslogic.media.Media(MediaId);

    // TODO: Remove "Magic strings" from code.
    try 
    {
     fileName = m.getProperty("fileName").Value.ToString();
    } 
    catch 
    {
     try 
     {
      fileName = m.getProperty("umbracoFile").Value.ToString();
     } 
     catch 
     {
      fileName = m.getProperty("file").Value.ToString();
     }
    }

    altText = m.Text;
    try 
     {
     fileWidth = int.Parse(m.getProperty("umbracoWidth").Value.ToString());
     fileHeight = int.Parse(m.getProperty("umbracoHeight").Value.ToString());
     }
    catch {
    
    }
    string fileNameOrg = fileName;
    string ext = fileNameOrg.Substring(fileNameOrg.LastIndexOf(".")+1, fileNameOrg.Length-fileNameOrg.LastIndexOf(".")-1);
    string fileNameThumb = GlobalSettings.Path + "/.." + fileNameOrg.Replace("."+ext, "_thumb.jpg");
    image.Controls.Add(new LiteralControl("<a href=\"" + GlobalSettings.Path + "/.." + fileNameOrg + "\" title=\"Zoom\"><img src=\"" + fileNameThumb + "\" border=\"0\"/></a>"));
   } 
   catch {
   }
   image.Controls.Add(new LiteralControl("<script>\nparent.updateImageSource('" + GlobalSettings.Path + "/.." + fileName.Replace("'", "\\'") + "','"+altText+"','" + fileWidth.ToString() + "','" + fileHeight.ToString() + "')\n</script>"));
  }
 }
}



Let's ignore the shamefullness of the code. We've all had our bad moments, and open source projects are bound to have some things like this. Lets comment on what the code does.

Notice this line:
string fileName = "/blank.gif";
Now notice that the bottom catch is empty. This catch is hit whenever a file does not have one of these attibutes:fileName, umbracoFile, file. So filename is never set to anything.

The solution
The last catch needs to get your custom media type. In our case:
Media m = new Media(MediaId);
fileName = m.getProperty("Image").Value.ToString();

That is the fix for Umbraco. So you have a couple options on how to implement it.

1. You can recompile Umbraco's source then redeploy.

2. If you don't want to recompile Umbraco you can can create a new file called ImageViewer.cs that inherits from umbraco.dialogs.imageViewer. Copy and paste the code, do your fix, then modify the ~/umbraco/dialogs/imageViewer.aspx file to inherit from your new file.

I.e. Change the header:
<%@ Page Language="c#" CodeBehind="imageViewer.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.imageViewer" %>

To this:
<%@ Page Language="c#" CodeBehind="MyImageViewer.aspx.cs" AutoEventWireup="True" Inherits="My.Project.UmbracoExtensions.MyImageViewer" %>

The downside of both methods is that you will overwrite your changes if you upgrade Umbraco.


The awesome solution
Here is a solution that is awesomely hilarious. Using the already installed Urlrewriting.Net module that comes with Umbraco.

1. Create a new file called ImageViewer.aspx in your project somewhere. Mine was in ~/UmbracoExtensions/

Make the code behind inherit from umbraco.dialogs.imageViewer. Copy and paste the code, do your fix.

2. Open ~/config/UrlRewriting.config and add this entry:

<add name="ImageViewer"
          virtualUrl="~/umbraco/dialogs/imageViewer.aspx(.*)"
          rewriteUrlParameter="IncludeQueryStringForRewrite"
          redirectMode="Permanent"
          destinationUrl="~/UmbracoExtensions/ImageViewer.aspx$1"
          redirect="Application"
          ignoreCase="true" />

This redirects ~/umbraco/dialogs/imageViewer.aspx to your new ~/UmbracoExtensions/ImageViewer.aspx and passes the query string which allows the lookup of the file.

3. Open your ~/web.config and add /UmbracoExtensions/ to your umbracoReservedPaths under appSettings. This allows your newly created file to be redirected to.

That's it!

I find this solution absolutely hilarious! But it means I will not get undone by an upgrade. Hopefully they will eventually fix the imageViewer class to look for other files.

UPDATE
Ok after all that, I found out that you just need to change the alias name in the cmsPropertyType in the database.

First check if your file alias is being used more than once. In our case we had an Image file type, and a field in a document type called Image. So we have to modify the correct one.

Here is how to check your table:
SELECT * FROM [YOUR_DATABASE].[dbo].[cmsPropertyType]
  WHERE Alias LIKE 'Image'

To figure out which was which I did this really lazily. I had a synced db already on dev, so I just changed the alias of one to see if it would change in Umbraco. Note that you must bump the config for the alias to change in Umbraco.

Here is the sql to update your file type alias:
UPDATE [YOUR_DATABASE].[dbo].[cmsPropertyType]
  SET Alias = 'umbracoFile'
  WHERE Alias in ('YOUR_FILE_TYLE_ALIAS') // for multiple file types
  AND id !=  // line is optional


So it turns out there was no coding involved after all. Well at least I learned heaps about how Umbraco was structured :)



Saturday 23 January 2010

How to insert a link tag in Blogger / Blogspot html - Hacking Blogger without JQuery



In my previous post I showed how it was possible to insert a CSS file into the head element of a Blogger / Blogspot page, at load time using JQuery. This is useful because Blogger does not allow you to add <link> tags in the "Edit HTML" tab.

I decided that JQuery was a bit overkill. Ok shitloads overkill. It's better to just do it with a small amount of javascript. The following code will insert a <link> tag into the head at load time.

function insertCSS(filename) {
    var link = document.createElement("link")
    link.setAttribute("rel", "stylesheet")
    link.setAttribute("type", "text/css")
    link.setAttribute("href", filename)
    document.getElementsByTagName("head")[0].appendChild(link)
}
insertCSS("http://your.site/tools/libraries/prettify.css");

I'm talking within the context of being able to prettyprint your code, so I put this code into a file called bloggerPrettifyHack.js. This allows me to simply include 2 javascript files - bloggerPrettifyHack.js, and prettify.js.
<script type="text/javascript" src="http://your.site/tools/libraries/bloggerPrettifyHack.js"></script>
<script type="text/javascript" src="http://your.site/tools/libraries/prettify.js"></script>

Then at the bottom of my page I make the call to colorize :)
<script type="text/javascript">
  prettyPrint();
</script>

Note: I tried to add the prettify.js file dynamically using the same technique. It loads just fine, however the pretty-printing doesn't work. I'm guessing it's something to do with event firing. Ideally I'd like to have a single .js file to which does everything. Can someone can provide a quick (not too nasty) workaround?


Update:
Due to a few code posts, I noticed multiple duplicates of the CSS file in the <head> tag. Also the prettyprint.js was seen in the content sections of each post. To combat this I decided to do a simple check and delete. Here's the modified code.
function insertCSS(filename) 
{
    var inHead = alreadyInHead(filename);    
    if (inHead == true)
        return;
    
    var link = document.createElement("link")
    link.setAttribute("rel", "stylesheet")
    link.setAttribute("type", "text/css")
    link.setAttribute("href", filename)
    document.getElementsByTagName("head")[0].appendChild(link)
}

function alreadyInHead(filename) {
    var links = document.getElementsByTagName('link');
    
    for (var i = 0; i < links.length; i = i +1)
    {
        var link = links[i];
        var href = link.getAttribute("href");
        if (href == filename) {
            return true;           
        }
    }
    return false;
}

function remove(filename) 
{
    var scripts = document.getElementsByTagName('script');
    for (var i = 0; i < scripts.length; i = i + 1) {
        var node = scripts[i];
        var src = node.getAttribute("src");
        if (src == filename) {
            node.parentNode.removeChild(node);
            i -= i - 1;
        }
    }
}

insertCSS("http://your.site/tools/libraries/prettify.css");
remove("http://your.site/tools/libraries/bloggerPrettifyHack.js");
remove("http://your.site/tools/libraries/prettify.js");

I'm still cringing because there are still multiple calls of prettyPrint(). Can anyone help out with this one?

How to insert a link tag in Blogger / Blogspot html - Hacking Blogger with JQuery



This article will show you how to insert a <link> tag in the header of the html when in the "Edit HTML" tab of Blogger/Blogspot. The example I give involves using the prettify library.

The background behind why I want to do this
I wanted to use prettify to colorize my code in blogspot. This fantastic awesome tool requires only a .js file and some CSS. Blogger allows you to insert at link to an external Javascript file in the "Edit HTML" tab. It does not however allow an external CSS file. I find this really annoying. I do not want to copy and paste all this CSS every time I insert code. So I thought, if I can insert a Javascript file then maybe I'll just hack it with my favorite tool JQuery :)

The first order of business is to include JQuery:
<script src="http://your.site/tools/libraries/jquery-1.4.min.js" type="text/javascript">
</script>

Next we insert the CSS file into the <head> element:
<script type="text/javascript">
    function hackHead() {
        var src = '<link type="text/css" rel="stylesheet"  href="http://your.site/tools/libraries/prettify.css"/>'
        $("head").append(src);
    }
    hackHead();
</script>

Now we add the Prettify library.
<script src="http://your.site/tools/libraries/prettify.js">
</script> 

Finally, at the very bottom of your page you need need to call prettyPrint()
<script type="text/javascript">
  prettyPrint();
</script>


Update: Ok I give in. JQuery is overkill. See my new post for a neater way to do this.

Random Code Generator in C#

I needed to generate a whole bunch of random codes so I wrote a little class to do it for me. Here is the code for it

public class CodeGenerator
    {
        private const string Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$-_.+!*'(),";
        private Random m_Random;
        
        public CodeGenerator()
        {
            m_Random = new Random();
        }

        public List<string> GenerateRandomCodes(double noOfCodes, int codeLength)
        {
            double combinations = MathLibrary.GetCombinations(Characters.Length, codeLength);

            // check permutations
            if (noOfCodes &gt; combinations)
                throw new Exception(String.Format("noOfCodes &gt; maximum combinations. I.e. noOfCodes &gt; {0}", combinations));

            List<string> codes = new List<string>();
            for (long i = 0; i &lt; noOfCodes; )
            {
                string code = GenerateCode(codeLength);
                if (!codes.Contains(code))
                {
                    codes.Add(code);
                    i++;
                }
            }
            return codes;
        }

        public string GenerateRandomCode(int codeLength)
        {
            return GenerateCode(codeLength);
        }

        private string GenerateCode(int codeLength)
        {
            string code = "";

            // select random character 
            for (int i = 0; i &lt; codeLength; i++)
                code += Characters[m_Random.Next(Characters.Count())];

            return code;
        }
    }


Oh and my Math Library

public class MathLibrary
    {
        public static double GetCombinations(int n, int k)
        {
            double top = Factorial(n + k - 1);
            double bottomLeft = Factorial(k);
            double bottomRight = Factorial(n - 1);

            return (double)top / (bottomLeft * bottomRight);
        }

        public static double Factorial(int n)
        {
            return ((n &lt;= 1) ? 1 : (n * Factorial(n - 1)));
        }
    }

Thursday 21 January 2010

Ninite Review

Ninite is the most useful piece of software I've used in a while.

It downloads and installs all the software while you go out and play. No longer must I sit there clicking, waiting, and restarting - a process that in the past took 2 hours, not to mention configuration eeek!

I've tried 
Ninite on a Windows XP and 7 machine.

The procedure could not be more simple:

Go to the website
Select the checkboxes of the software you want to install. Don't bother with trial software, you'll just have to uninstall it later.
Click "Get Installer".
Run the installer and go for lunch.

General Comments

It would be better with some intelligent download threading (which they will probably implement soon anyway). Essentially, I'd like to see at least 2 intelligent download threads. E.g. Start downloading the largest file at the beginning so it will finish by the time the other packages are downloaded and installed. Also, I'm not likely to be using my computer while all this software is being installed.

Conclusion

This software is amazing! If you've formatted your computer and you want your software be installed auto-magically then this is the software for you.


Chrome OS - First Impressions


I downloaded the new Google Chrome OS and ran it on Sun’sVirtualBox VM software.

My first impressions...

It was only an 8 second start up time to the login screen. Awesome! Then a single sign-on to your computer with your Gmail account. Another 8 seconds and the Chrome browser was open with my Gmail account and Google Calander. Now that was impressive! Within 25 seconds of me turning on my computer I was reading my email.


What else is there to Chrome OS? It doesnt seem like there is anything else. I couldn't find anywhere to configure system preferences or anything. Do you want to open other programs or windows, or install other software (like Firefox)? Unfortunately you can't! It felt like I was in an internet cafe with a computer that is so locked down that all I can do is surf the web. A note to all internet cafe's around the world...None of the big players online support IE6 anymore. Please upgrade to anything else!


Anyway, Chrome OS is aimed at netbooks (at least initially). A netbook user would typically like to turn on their computer, have it quickly boot, have their browser immediately open, check their mail and calendar, and then continue surfing. That is what Chrome OS allows them to do, but thats it. The OS is merely a portal to the internet. You can't run anything locally.


But I guess that over the years Google has been introducing online services and applications so the average consumer doesn't really need local applications. With Chrome OS, Google can encourage the use of online applications such as Google Documents, and (when it gets a lot better) it might actually start to rival MS Office, because it is free and online. After all, who needs local storage when there is cloud computing?


Conclusion... Google Chrome OS boots damn fast! It allows you to quickly get on the interweb. It's not exciting from an OS perspective as far as user experience goes, but damn it's fast!

If you want to try out, navigate your interweb browser to this hyperlink:http://www.techcrunch.com/2009/11/19/guide-install-google-chrome-os/

P.s. Google seems to be putting all types of applications online. What is next? Is Google going to make an online Pro Tools? Now that would be impressive!



Meta tags and Umbraco


You’d think that you could do something like this to get meta description and keywords from umbraco
 <meta name="description" content="" />

Unfortunately umbraco will render this:
<meta name="description" content="<umbraco:Item runat='server' field='PageDescription'/>" />

The solution is inline xslt:
<umbraco:Item ID="Item" Field="PageDescription" runat="server" Xslt="concat('')"XsltDisableEscaping="true">umbraco:Item>


*Duplicated from my post on Farmcode.org


A problem using sIFR for menus that link to databound forms in IE7


This is quite possibly the most interesting/annoying/crazy quirk/bug I have ever come across.

Scenario

Your site has a sIFR menu. One of the links goes to a typical databound form (in our case an update user details form).

Problem

The user modifies a field and submits. The database table shows the change. However, when you navigate back to the form using your sIFR menu link, you notice the old value(s) in the fields. The correct values only appear once you clear your cache. This problem only occurs in IE7.

Reason

It turns out that the sIFR link is not a normal link. It is essentially like hitting the enter key in the address bar of a page that is already loaded. The browser merely loads from its cache. No matter how many times you click that link!

Solution

In the code behind of your form on page load call this:

Response.Cache.SetCacheability(HttpCacheability.NoCache);





*Duplicated from my post on Farmcode.org

Tuesday 19 January 2010

Programmers are Gods

I found this post very amusing.


"So if you’re working with a programmer, you have to treat him or her like a God. You have to pray. You cannot issue edicts. You have to come on bended knee. “Here’s the problem I have. I need a solution. Please help.”"

"if you give a programmer a problem with parameters, they’ll apply every bit of genius they have to solve it in the best possible way. If you tell them how to do it, you’ll suffer the wrath of an angry God."

This is entirely true. We hate being told to do something. And we hate being told by someone (who is not a programmer) how to make/fix something.

Classic ASP in Visual Studio 2008 on IIS6 and IIS7 on Windows XP and Windows 7

I had to migrate a couple sites in classic ASP. I also had to make some modifications. I had to do these on  Windows XP (IIS6) and Windows 7 (IIS7) so I thought I'd write a post before I forget how to do it all.

Classic ASP in Visual Studio
Visual Studio 2008 has great intellisense for html, javascript etc. so it's definitely one of the nicest options for an IDE. According to a few posts I saw, you need SP1 for Visual Studio 2008. I already had it installed. 


In Visual Studio 2008, create a new Website (NOT Web Project). It does not make sense to create a Web Project because ASP doesn't require you to compile it. Likewise. "Websites" in Visual Studio 2008 are not precompiled before publishing. Copy the site to the root folder. In Visual Studio click the refresh button at the top of the Solution Explorer.


As far as I'm aware (correct me if I'm wrong), you cannot run ASP on Cassini (Visual Studio's lightweight web server). So you have to run your site using IIS (install it form your windows disk if you don't have it).


If you're running Windows XP, here is a nice tool that enables you to set up multiple of sites in IIS. Note that you can only have one site started at a time, however if you're constantly having to switch between sites you're testing/developing etc then this tool is rad!


Running Classic ASP in IIS
In IIS6 and 7, "enable parent paths" is disabled. You must enable it for your website, otherwise your ASP pages that access files in their parent folder (e.g. trying to access ../some_file.asp) will throw an error.


In IIS6 (Windows XP)
  1. Right-click the website, and then click Properties.
  2. Select Home Directory, and then click Configuration.
  3. Click Options, and select the Enable Parent Paths check box.
In IIS7 (Windows Vista and Windows 7)
  1. Click the website, and then click ASP (under the IIS section)
  2. Under Enable Parent Paths (5th item in Behavior section), select true.
  3. While you're there, select Send Errors To Browser (so you can see any errors)
  4. Click Apply.
Note that in II7 you have to install ASP as it is not installed by default. It's real simple. Just do the following:
  1. Go to Turn Windows Features on or off under Programs and Features (control panel).
  2. Expand Internet Information Services, then World Wide Web Services, then Application Development Features.
  3. Select ASP, and then click OK.
Now assuming you pointed the IIS to the correct folder, you should be able to see your website in your browser (in anything other than IE6) via http://localhost


I hope this was informative. If I left anything out, please let me know.



Google vs China


Ok so this is huge! They all but accuse the Chinese government of espionage: 


First blog

Nuff said