Simple CMS with Linq to SQL

A very basic content management system with multiple nested pages, comments and maybe a basic user management interface. Everything will be in ASP.Net MVC 2 in C#.

I’ve been meaning to do this for quite some time, but work and life kept getting in the way. I thought if I’m going to update for real, I’d at least post something useful.

I’ll be making use of my previous Linq to SQL Membership and Role providers for this to save time and effort. There are some minor alterations in the code, but those should be easy to make.

It would be helpful to have some prior knowledge of MVC basics, but not an absolute prerequisite. Things will go a bit smoother if you’re already familiar with it and Visual Web Developer Express, which my IDE of choice for this one. I’m also going to be using Sql Server Express 2008 and I won’t be adding an MDF to the App_Data folder as most quick examples would show, but create a database in SQL Server using the Management Studio instead. I think it’s far simpler to deploy to a hosted service with this starting point.

Because I’m going to be posting a lot of code in this, there is the potential for mistakes in my train of thought or during formatting. Please point them out if you happen to come across them or if you have improvements. But please keep in mind that this is a SimpleCMS, so we’re not going to get all that fancy ;)

Let’s start with a ContentPage table in your database :

Don't forget to set PageId as the primary key and set properties to auto increment.

Note that only three fields are required and one is by default because it’s the primary key. This is because I thought of having the most flexible arrangement program-wise without being concerned too much about required fields in the table. And because I wanted the CMS to double as a forum at some point… More on that later.

Now we need a ContentComments table :

Very important not to confuse AuthorId with AuthorIP. Seems silly to point that out, but it's happened to me ;)

Now we need to add a parent-child relationship by adding a foreign key to the ContentComments table. In this case, the PageId of the ContentComments table is going to be the foreign key to the ContentPages table’s PageId primary.

When adding relationships, it's better to keep the Primary and Foreign key field names the same.

Now back in your solution explorer, right click on the Models folder and Add > New Item. We’re going to add the Linq to SQL data classes here and call it SimpleCMS (you can call it any name of your choice, but just remember to keep it matching the project name for simplicity.

You’ll see the classes designer open with two sections exposed. You need to drag and drop the ContentPages and ContentComments tables into the class designer view.

Just remember to compile your project after adding these tables (or making any changes to the dbml) or else the next steps won't work.

Now that the database is set, we’re on our way to the fun part; the classes. But first, I’m going to break one of my own rules here and develop the template. This is because the default MVC theme is a bit too bare and, I believe, not very flexible even if you decide to hack it to pieces.

We’ll be using a version of the mod portal variation of one of my old template tutorials.

Onward to the next step…

ASP.Net MVC Friendly Dates

Welcome to the colony… Well, over the weekend I caught a terrible bug and had to miss work today. While I’m laying here in bed, I thought I might post something (yes, I’m that bored).

While poking around my old CMS, I thought about how I went about using friendly dates on all the forums and blog posts. Usually I’d use an external function, but with MVC, you can extend the HtmlHelper class directly and apply the new function right in the view.

I.E. Instead of…

 <%= Html.Encode(item.LastModified)%>

Use…

 <%= Html.FriendlyDate(item.LastModified)%>

The premise is fairly simple and you can probably implement something more efficient than this. But considering I feel like my head is in a vise right now, this would have to do.

using System;
using System.Text;
using System.Web;
using System.Web.Mvc;

// Important to use the default Mvc.Html namespace as that's what we're extending
namespace System.Web.Mvc.Html
{
	public static class HtmlExtensions
	{
		public static string FriendlyDate(this HtmlHelper helper, Object ddt)
		{
			// Get all the variables set
			DateTime dt = (DateTime)ddt;
			DateTime now = DateTime.Now;
			StringBuilder sb = new StringBuilder();
			TimeSpan elapsed;

			int days = 0;
			int hours = 0;
			int minutes = 0;
			int years = 0;

			// If the given date is *before* now
			if (now > dt)
				elapsed = now.Subtract(dt);
			// If the given date is *after* now
			else
				elapsed = dt.Subtract(now);

			// Leap years are always fun... 
			// I don't care too much about accuracy after the current year
			if (DateTime.IsLeapYear(dt.Year))
				years = (int)(elapsed.Days / 366);
			else
				years = (int)(elapsed.Days / 365);

			days = (int)elapsed.TotalDays;
			hours = (int)elapsed.TotalHours;
			minutes = (int)elapsed.TotalMinutes;

			// There aren't any hours days or years yet
			if (years <= 0 && days <= 0 && hours <= 0)
			{
				if (minutes > 0)
				{
					sb.Append(minutes.ToString("0") + " minute");

					if (minutes > 1)
						sb.Append("s");
				}
			}
			else
			{
				// If we have years, then days and hours don't really mean much
				if (years > 0)
				{
					if (years < 10)
					{
						sb.Append(years.ToString("0") + " year");
						if (years > 1)
							sb.Append("s");
					}
					// ... Hey, ASP lasted quite a while. You just never know.
					else if (years > 10 && years < 30)
					{
						int dec = years / 10;
						sb.Append(dec + " decade");
						if (dec > 1)
							sb.Append("s");
					}
					else
					{
						sb.Append("A very long time");
					}
				}
				else
				{
					// We have days and hours
					if (days > 0)
					{
						sb.Append(days.ToString("0") + " day");
						if (days > 1)
							sb.Append("s");
					}
					if (hours > 0)
					{
						sb.Append(" " + hours.ToString("0") + " hour");
						if (hours > 1)
							sb.Append("s");
					}
				}
			}

			if (now > dt)
				// Past time
				sb.Append(" ago");
			else
				// Future time
				sb.Append(" from now");

			return sb.ToString();
		}
	}
}

ASP.Net BBCode (C#)

Update

This code has now been superceeded by a better alternative that will allow you to use an off-the-shelf WYSIWYG and still allow custom tags.

This problem comes up if you find yourself creating a forum from scratch or implementing some sort of comment system and want to make sure you can introduce some formatting functionality without compromising security.

Well, there are plenty of regular expressions examples out there, but few deal directly with BBCode and of those, most don’t go beyond the basic Bold, Italic, and Strike formatting plus HTML links, images etc…

This example not only formats the above basic stuff, but also does quotes, alignment, Google search links, Wikipedia article links, as well embedded videos for several video sharing sites. You can always add more tags by following the same pattern. All the content is formatted into paragraphs (<p>) for proper validation. It checks for nested quotes up to a specified depth.

There is no extensive input cleanup to prevent XSS attacks through tags. I’m just showing the basics like tag stripping which can be circumvented by clever attakers, so it’s up to you to implement a more thorough system. The reason I’m excluding it is because there are already many, many, many examples out there that do a wonderful job at it.

This does set a limited set of formatting options.

To make everything easier to read in the code file, I used multiple Regex replacements instead of one super duper pattern. This also makes adding quick tags for something else much simpler.

A sample of the rendered markup :

  • [b]Bold[/b] = Bold
  • [i]Italic[/i] = Italic
  • [del]Strike[/de] = Strike
  • [color=blue]Blue[/blue] = Blue
  • [color=#FA9A99]Pinkish[/blue] = Pinkish
  • [size=2]Larger text[/size] (between 2 & 5) = Larger text
  • [google]once in a blue moon[/google] = once in a blue moon
  • [wikipedia]Captain Haddock[/wikipedia] = Captain Haddock (Remember, Wikipedia articles are case sensitive)
  • [img]http://www.google.com/logos/Logo_40blk.gif[/img] =
  • [img=Google Logo]http://www.google.com/logos/Logo_40blk.gif[/img] = Google Logo

One major difference than other BBCode functions is the ability to embed YouTube, Metacafe and LiveVideo media players. You only need to specify the URL within the tags.

E.G. For youtube :


For LiveVideo
[livevideo]http://www.livevideo.com/video/Megalis/E7C2EEE8A7C740379F40B5ECA56ACE8A/momma-sophia-s-dream.aspx[/livevideo]

I don’t think WordPress supports embedding Metacafe videos at the time of this post, but if you want to include those videos :
[metacafe]http://www.metacafe.com/watch/685732/the_diet/[/metacafe]

To find the exact URL of the LiveVideo page, click on “Get Codes” link underneath the video player.
I tried to keep this as simple as possible for users as they just need to wrap the url around [tag][/tag] markers.

You can embed quotes following a similar convention to phpBB. But there are slight differences as this was for a custom application.

[quote]This is a quote[/quote]

This is a quote

[quote=Author]This is a quote[/quote]

Author wrote


This is a quote

And so on…

This version also deals with Headers
([h#]Header[/h#] becomes <h#>Header</h#> from 1 to 6.)

[h3]Header[/h3]

[h4]Header[/h4]

And so on…

Of course, you would want to apply special formatting via CSS to keep the look of the page consistent with the rest of the site.

This particular excerpt was written for a .Net 3.5 app, but this portion should work on 2.0+ with no alterations since it doesn’t use anything unique to the newer framework.

This class is by no means meant to be a comprehensive bbcode plugin, but it should get you on the way to create your own custom tags.

Once again, this code has no usage restrictions. I’m just including a disclaimer like all other code samples I’ve posted here. You don’t have to ask me permission to use it for any purpose and I only ask that you abide by the disclaimer.

THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

The tag function

/// <summary>
/// Converts the input plain-text BBCode to HTML output and replacing carriage returns
/// and spaces with <br /> and   etc...
/// Recommended: Use this function only during storage and updates.
/// Keep a seperate field in your database for HTML formatted content and raw text.
/// An optional third, plain text field, with no formatting info will make full text searching
/// more accurate.
/// E.G. BodyText(with BBCode for textarea/WYSIWYG), BodyPlain(plain text for searching),
/// BodyHtml(formatted HTML for output pages)
/// </summary>
public static string ConvertToHtml(string content)
{
    // Clean your content here... E.G.:
    // content = CleanText(content);

    // Basic tag stripping for this example (PLEASE EXTEND THIS!)
    content = StripTags(content);

    content = MatchReplace(@"\[b\]([^\]]+)\[\/b\]", "<strong>$1</strong>", content);
    content = MatchReplace(@"\[i\]([^\]]+)\[\/i\]", "<em>$1</em>", content);
    content = MatchReplace(@"\[u\]([^\]]+)\[\/u\]", "<span style=""text-decoration:underline"">$1</span>", content);
    content = MatchReplace(@"\[del\]([^\]]+)\[\/del\]", "<span style=""text-decoration:line-through"">$1</span>", content);

    // Colors and sizes
    content = MatchReplace(@"\[color=(#[0-9a-fA-F]{6}|[a-z-]+)]([^\]]+)\[\/color\]", "<span style=""color:$1;"">$2</span>", content);
    content = MatchReplace(@"\[size=([2-5])]([^\]]+)\[\/size\]", "<span style=""font-size:$1em; font-weight:normal;"">$2</span>", content);

    // Text alignment
    content = MatchReplace(@"\[left\]([^\]]+)\[\/left\]", "<span style=""text-align:left"">$1</span>", content);
    content = MatchReplace(@"\[right\]([^\]]+)\[\/right\]", "<span style=""text-align:right"">$1</span>", content);
    content = MatchReplace(@"\[center\]([^\]]+)\[\/center\]", "<span style=""text-align:center"">$1</span>", content);
    content = MatchReplace(@"\[justify\]([^\]]+)\[\/justify\]", "<span style=""text-align:justify"">$1</span>", content);

    // HTML Links
    content = MatchReplace(@"\[url\]([^\]]+)\[\/url\]", "<a href=""$1"">$1</a>", content);
    content = MatchReplace(@"\[url=([^\]]+)]([^\]]+)\[\/ur\l]", "<a href=""$1"">$2</a>", content);

    // Images
    content = MatchReplace(@"\[img\]([^\]]+)\[\/img\]", "<img src=""$1"" alt="""" />", content);
    content = MatchReplace(@"\[img=([^\]]+)]([^\]]+)\[\/img\]", "<img src=""$2"" alt=""$1"" />", content);

    // Lists
    content = MatchReplace(@"\[*\]([^\[]+)", "<li>$1</li>", content);
    content = MatchReplace(@"\[list\]([^\]]+)\[\/list\]", "<ul>$1</ul><p>", content);
    content = MatchReplace(@"\[list=1\]([^\]]+)\[\/list\]", "</p><ol>$1</ol><p>", content);

    // Headers
    content = MatchReplace(@"\[h1\]([^\]]+)\[\/h1\]", "<h1>$1</h1>", content);
    content = MatchReplace(@"\[h2\]([^\]]+)\[\/h2\]", "<h2>$1</h2>", content);
    content = MatchReplace(@"\[h3\]([^\]]+)\[\/h3\]", "<h3>$1</h3>", content);
    content = MatchReplace(@"\[h4\]([^\]]+)\[\/h4\]", "<h4>$1</h4>", content);
    content = MatchReplace(@"\[h5\]([^\]]+)\[\/h5\]", "<h5>$1</h5>", content);
    content = MatchReplace(@"\[h6\]([^\]]+)\[\/h6\]", "<h6>$1</h6>", content);

    // Horizontal rule
    content = MatchReplace(@"\[hr\]", "<hr />", content);

    // Set a maximum quote depth (In this case, hard coded to 3)
    for (int i = 1; i < 3; i++)
    {
        // Quotes
        content = MatchReplace(@"\[quote=([^\]]+)@([^\]]+)|([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 <a href=""" + QuoteUrl("$3") + """>wrote</a> on $2</cite><hr /><p>$4</p></blockquote></div></p><p>", content);
        content = MatchReplace(@"\[quote=([^\]]+)@([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 wrote on $2</cite><hr /><p>$3</p></blockquote></div><p>", content);
        content = MatchReplace(@"\[quote=([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 wrote</cite><hr /><p>$2</p></blockquote></div><p>", content);
        content = MatchReplace(@"\[quote\]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><p>$1</p></blockquote></div><p>", content);
    }

    // The following markup is for embedded video -->

    // YouTube
    content = MatchReplace(@"\http:\/\/([a-zA-Z]+.)youtube.com\/watch\?v=([a-zA-Z0-9_\-]+)\[\/youtube\]",
        "<object width=""425"" height=""344""><param name=""movie"" value=""http://www.youtube.com/v/$2""></param><param name=""allowFullScreen"" value=""true""></param><embed src=""http://www.youtube.com/v/$2"" type=""application/x-shockwave-flash"" allowfullscreen=""true"" width=""425"" height=""344""></embed></object>", content);

    // LiveVideo
    content = MatchReplace(@"\[livevideo\]http:\/\/([a-zA-Z]+.)livevideo.com\/video\/([a-zA-Z0-9_\-]+)\/([a-zA-Z0-9]+)\/([a-zA-Z0-9_\-]+).aspx\[\/livevideo\]",
        "<object width=""445"" height=""369""><embed src=""http://www.livevideo.com/flvplayer/embed/$3"" type=""application/x-shockwave-flash"" quality=""high"" width=""445"" height=""369"" wmode=""transparent""></embed></object>", content);

    // LiveVideo (There are two types of links for LV)
    content = MatchReplace(@"\[livevideo\]http:\/\/([a-zA-Z]+.)livevideo.com\/video\/([a-zA-Z0-9]+)\/([a-zA-Z0-9_\-]+).aspx\[\/livevideo\]",
        "<object width=""445"" height=""369""><embed src=""http://www.livevideo.com/flvplayer/embed/$2&autostart=0"" type=""application/x-shockwave-flash"" quality=""high"" width=""445"" height=""369"" wmode=""transparent""></embed></object>", content);

    // Metacafe
    content = MatchReplace(@"\[metacafe\]http\:\/\/([a-zA-Z]+.)metacafe.com\/watch\/([0-9]+)\/([a-zA-Z0-9_]+)/\[\/metacafe\]",
        "<object width=""400"" height=""345""><embed src=""http://www.metacafe.com/fplayer/$2/$3.swf"" width=""400"" height=""345"" wmode=""transparent"" pluginspage=""http://www.macromedia.com/go/getflashplayer"" type=""application/x-shockwave-flash""></embed></object>", content);

    // LiveLeak
    content = MatchReplace(@"\[liveleak\]http:\/\/([a-zA-Z]+.)liveleak.com\/view\?i=([a-zA-Z0-9_]+)\[\/liveleak\]",
        "<object width=""450"" height=""370""><param name=""movie"" value=""http://www.liveleak.com/e/$2""></param><param name=""wmode"" value=""transparent""></param><embed src=""http://www.liveleak.com/e/59a_1231807882"" type=""application/x-shockwave-flash"" wmode=""transparent"" width=""450"" height=""370""></embed></object>", content);

    // < -- End video markup

    // Google and Wikipedia page links
    content = MatchReplace(@"\[google\]([^\]]+)\[\/google\]", "<a href=""http://www.google.com/search?q=$1"">$1", content);
    content = MatchReplace(@"\[wikipedia\]([^\]]+)\[\/wikipedia\]", "<a href=""http://www.wikipedia.org/wiki/$1"">$1</a>", content);

    // Put the content in a paragraph
    content = "</p><p>" + content + "</p>";

    // Clean up a few potential markup problems
    content = content.Replace("t", "    ")
        .Replace("  ", "  ")
        .Replace("<br />", "")
        .Replace("<p><br />", "</p><p>")
        .Replace("</p><p><blockquote>", "<blockquote>")
        .Replace("</blockquote></blockquote></p>", "")
        .Replace("<p></p>", "")
        .Replace("<p><ul></ul></p>", "<ul>")
        .Replace("<p></p></ul>", "")
        .Replace("<p><ol></ol></p>", "<ol>")
        .Replace("<p></p></ol>", "")
        .Replace("<p><li>", "</li><li><p>")
        .Replace("</p></li></p>", "");

    return content;
}

StripTags and Match Replace functions

/// <summary>
/// Strip any existing HTML tags
/// </summary>
///
<param name="content">Raw input from user</param>
/// <returns>Tag stripped storage safe text</returns>
public static string StripTags(string content)
{
	return MatchReplace(@"< [^>]+>", "", content, true, true, true);
}

public static string MatchReplace(string pattern, string match, string content)
{
	return MatchReplace(pattern, match, content, false, false, false);
}

public static string MatchReplace(string pattern, string match, string content, bool multi)
{
	return MatchReplace(pattern, match, content, multi, false, false);
}

public static string MatchReplace(string pattern, string match, string content, bool multi, bool white)
{
	return MatchReplace(pattern, match, content, multi, white);
}

/// <summary>
/// Match and replace a specific pattern with formatted text
/// </summary>
///
<param name="pattern">Regular expression pattern</param>
///
<param name="match">Match replacement</param>
///
<param name="content">Text to format</param>
///
<param name="multi">Multiline text (optional)</param>
///
<param name="white">Ignore white space (optional)</param>
/// <returns>HTML Formatted from the original BBCode</returns>
public static string MatchReplace(string pattern, string match, string content, bool multi, bool white, bool cult)
{
	if (multi && white && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
	else if (multi && white)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.IgnoreCase);
	else if (multi && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
	else if (white && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant);
	else if (multi)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline);
	else if (white)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
	else if (cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

	// Default
	return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase);
}

Enjoy!

Addendum…
Robert Beal, in particular has created a wonderful HtmlUtility class for C# 3.0+ that will only allow certain tags and tag attributes. If your visitors make use of extensive HTML tags, then that is a better option than my system. If you want to implement a feedback system for guest writers with strong HTML support, Robert’s example is highly recommended.

My example is really only for people who post in plain text most of the time, would only post formatted text and videos semi-rarely.

In fact, it’s probably best to avoid extensive HTML support for ordinary comments, as that will only encourage users to abuse the system. You’re better off using this for something minimal.

Symfony : The ASP.Net of PHP

… Well not quite, but I’m sure most ASP.Net programmers would refer to it in this fasion. If you’re coming form another web application framework, then this is well worth looking into.

This project came into focus for us when we had a client with a preexisting web application in need of a drastic rewrite. The catch was the time. We literally had 3 days!!

Well, considering (as I’ve mentioned before) I’m not the greatest PHP programmer out there. In fact, before this, my most extensive work with PHP was a discussion board and FAQ comment system tied to a CRM. But, as they say in England, you’ve got to keep a stiff upper lip at these types of situations.

I would highly recommend downloading Uniform Server if you’re on a Windows machine. It’s, by far, the easiest method of creating a complete Apache/PHP/MySQL platform painlessly for Windows systems. Also of note is the nice admin panel, which, though rather sparse, gets the job done and is easy to use.

The first step is pretty basic. Get your server running and then navigate to W: in a command prompt (This is where the server will mount when running).

Note : The commands to type are in bold white.
c:>w:
w:>cd usrlocalphp
w:usrlocalphp>

Now we’re going to use Pear to access the Symfony repository.
w:usrlocalphp>pear channel-discover pear.symfony-project.com

If your system for some reason cannot identify PHP (some users noted this problem), then use the full path of PHP.
w:usrlocalphp>w:usrlocalphppear channel-discover pear.symfony-project.com

Once that’s done, we’re going to install it
W:usrlocalphp>php -r "readfile('http://pear.php.net/go-pear');" > go-pear

Again, if it doesn’t recognize this, use the full path as the above example.
W:usrlocalphp>w:usrlocalphpphp -r "readfile('http://pear.php.net/go-pear');" > go-pear

Now you will be prompted with a few installation options.

Note: All these are without quotes…

For : “If you wish to abort, press Control-C now, or press Enter to continue: ”
Press : “Enter”

For : “HTTP proxy (http://user:password@proxy.myhost.com:port), or Enter for none:”
Press : “Enter”

For : “1-8, ‘all’ or Enter to continue:” (This is regarding the executable directory)
Press : “Enter”

For : “Would you like to install these as well? [Y/n] :”
Type : “y”

For : “Would you like to alter php.ini ? [Y/n] :”
Type : “y”

Now as of this post, the version you have just installed is the 1.1.x branch. It’s the stable branch and works well.

My suggestion afterwards is to visit the cookbook so you can quickly get in touch with the basics. Also take a peek at the video tutorial on the Admin Generator.  It isn’t very difficult, but be thorough when you go through this. Missed steps or oversights can cause some issues down the road, so double-check your work.

Aside from the command line fiddling and a few manual edits, Symfony can get a great deal accomplished in a very short period of time.

Using the Admin Generator, here is my experimental (and very minimal) MySQL database generated by the tool :

# This is a fix for InnoDB in MySQL >= 4.1.x
# It "suspends judgement" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;

#-----------------------------------------------------------------------------
#-- articles
#-----------------------------------------------------------------------------

DROP TABLE IF EXISTS `articles`;

CREATE TABLE `articles`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`description` VARCHAR(255),
`content` TEXT,
`abstract` TEXT,
`author_id` INTEGER NOT NULL,
`category_id` INTEGER default NOT NULL,
`is_published` INTEGER,
`date_pub` DATETIME,
`date_created` DATETIME,
PRIMARY KEY (`id`),
INDEX `articles_FI_1` (`author_id`),
CONSTRAINT `articles_FK_1`
FOREIGN KEY (`author_id`)
REFERENCES `users` (`id`),
INDEX `articles_FI_2` (`category_id`),
CONSTRAINT `articles_FK_2`
FOREIGN KEY (`category_id`)
REFERENCES `categories` (`id`)
)Type=InnoDB;

#-----------------------------------------------------------------------------
#-- users
#-----------------------------------------------------------------------------

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(50) NOT NULL,
`display_name` VARCHAR(255),
`first_name` VARCHAR(255),
`last_name` VARCHAR(255),
`email` VARCHAR(100) NOT NULL,
`avatar` VARCHAR(255),
`bio` TEXT,
PRIMARY KEY (`id`)
)Type=InnoDB;

#-----------------------------------------------------------------------------
#-- categories
#-----------------------------------------------------------------------------

DROP TABLE IF EXISTS `categories`;

CREATE TABLE `categories`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`abstract` TEXT,
`photo` VARCHAR(255),
`is_private` INTEGER,
PRIMARY KEY (`id`)
)Type=InnoDB;

# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;

Not the best example out there, but you can see what I was aiming for.

The great thing about this is that you can very quickly create the basic functionaly of any web application from CMS to CRM to Blog with plenty of flexibility and room to fix errors.
Highly recommended if this is the first time you’re dealing with a web framework. Also, a good way to improve your PHP.

This example was setup on a Vista PC.

50% To 70% Of all Programmers are con-artists

Someone please, please, please, for the love of Pete, prove me wrong!

I’ve said this a few times before. And time and time again (against my own hopes) I seem to be coming across more and more of these people. Considering how many programmers there are in the world, I guess there is still some comfort in knowing there are few of us out there who don’t pull competence (and talent, in some cases) out of thin air.

Let the record stand that I have never detailed my own programming and web design expertise on this blog before or any other site. I.E. I have never presented my education, how many years I’ve been working and in which specific companies.

I have never claimed superior knowledge than my peers, and I have never charged anyone or presented any information on this blog for self promotion. I have never advertised or solicited work on this blog or my own site and have never received any funds through this blog or my own site. I have, however, poked fun at my own field and peers for what I believe is a ridiculous sense of self preservation at the cost of quality work. You can find a few posts about that on my About page.

Whenever I witness a case of intellectual dishonesty on this scale, I have to respond. As much as it stings to do this, since I actually have to read this stuff before posting here, let the grind begin.

Enter Joanna Gadel…

Joanna, if you ever come across this post or someone else finds it for you, let it be known that I’m officially acusing you of being a charlatan. You are using half-truths, false logic and a flood of other misinformation in public articles to present yourself as someone who knows what she’s talking about. In fact you, madam, are pulling a “Princess Caraboo” in the web design world. Even though the articles are posted as “free” information, they are rubbish! And they artificially inflate your capabilities which is fraud, if you intent to use your new found prowess for financial gain.

People who have hired you in good faith, should be getting their money back (if they’ve paid you) for being so thoroughly cheated. And if any of your many articles are a hint (as you’ve obviously used them to promote yourself) they have been most definitely cheated.

Joanna is part of SEO Sydney, a company “experienced” in search engine optimization, yet is somehow confined to using a table based layout that adds tons of cruft that many search engines will ignore. A lot of search engines ignore the content on a page after a specific number of bytes. You are better off avoiding excessive markup to make sure your sites are properly indexed. SEO Sydney “experts” seemed to have overlooked this. But let’s move on…

Joanna has her misinformation posted on Articlebase, EzineArticles, GoArticles, and the king of all shameless plugs, Promotion World. And I’m sure there are many more places she has flooded the net with this rubbish. Feel free to browse all the sites. With the exception of a few good hints in some articles, it’s the same junk.

I’m going to forego a detailed debunking of all her articles as I can’t do it all in one post. But let’s get to a few nuggets of gold… Besides, anyone within the web design community and programming community can see this nonsense for what it is. Indeed anyone with an ounce of common sense would.

Why specialized custom development is needed for any business.

Note the “needed” part. What development would that be Joanna? The variety you offer?
Of course the “Custom development” keyphrase is linked to Vision & Solutions, another company affiliated with her. For a site that purports to support accessibility, why is it built with tables? Why don’t any of the links have titles or access keys? Why does the site fail the accessibility tests?

I couldn’t check out their contact page for the <fieldset> tags or any form accessibility since that page is down at the time of this post.

The entire article is a pandering session to plug her company. There’s no validity to the claim that custom development is “required for any business” since many businesses function very well to exceptionally well with off-the-shelf products (both Open and Closed source). “Custom” development is required only for specialzed services where no other solution will suffice.

But let’s move on…

Fundamentals of ASP.Net programming vs. PHP programming

Ah… One of my most favorite comparisons, since they inevitably end up being a completely subjective (and, more often than not, inaccurate) bullet list.

Since I’m not a PHP programmer, though I have built sites with it, I will be sticking to what I know best. And since Joanna claims to have “10 years experience” in the web design field, I will not be claiming any of the inaccuracies are “myths”. I will be calling them “lies” instead since, as an “expert”, she must know the reality of the situation at this point. BTW… She starts comparing ASP.Net to PHP, but from time to time, the sugar high starts to wear off and she compares ASP to PHP instead. But let’s get on with it…

  1. “To run ASP.Net programs first need to install[sic] IIS on a Windows server platform”
    Outright lie!
    I’m running ASP.Net “programs” (note, it’s not ‘sites’ or ‘projects’ according to her) right now on Debian and Apache using mod_mono.
  2. “Even the database connectivity is expensive for ASP, because it require[sic] MS-SQL product…”
    Outright lie!
    Applications have been developed for the ASP.Net platform that successfully use MS-SQL, Firebird, and MySQL. I have used Sqlite in one occasion on a production site and I am currently using Npgsql, to interface with PosgtreSQL for a project. In fact, any database that can be extrapolated with a data tier can be successfully used with ASP.Net. You can even write your own persistence framework in any of the .Net languages much the same way you can write one in C, Java or C++ for PHP. In this regard it has the same exact flexibility that PHP has.
  3. “PHP codes itself are[sic] very light in weight, a contract programmer who begins his career into PHP, does not felt any pressure to look the[sic] source code to understand. Whereas In ASP codes are not so easy to quick understand.”
    Um… What? Let’s set aside the fact, she just switched to “ASP” from “ASP.Net”; She has linked the “contract programmer” keywords to a programming contract site she is affiliated with. In other words, this “coding” thing is “magic” therefore you must not know how it works or make sure you submit to her completely arbitrary assessment on ease of use.
  4. “ASP has a similar like[sic] Visual Basic type of syntax that also linked[sic] to Microsoft products as well. On the other hand PHP codes[sic] are based on generally C++ language and the syntax, which is used in PHP, is[sic] quite similar to C/C++ syntax”
    Outright lie!
    Once again, she switches to ASP as a comparison. And PHP Is not C/C++.  The “similar syntax” argument falls flat on anyone who has tried their hand on one platform coming from another.  “similar syntax” makes the code familiar but that’s it. At the time of this post, I have written web applications in VB.Net, C#, Managed C++ as well as a couple of experiments in J#. There are people writing web apps in any of the available CLR languages. In fact, there are people writing apps in Python for .NET as well.
    ASP.Net, and indeed, the entire .Net framework is just that… A “framework”. You are not limited to one language.
  5. “PHP has inbuilt[sic] attributes like ftp, encryption methods, even email also[sic] from a web page but in ASP such attributes are not obtainable and for this reason only some more features are required which are not free that[sic] increase the total cost as well”
    Outright lie!
    You can use pre-written code available free from countless developer sites for those methods or, better yet, write, those methods for virtually any platform for which .Net is available to suite your own needs. In fact, the Mono people have done an excellent job at making many methods from ASP.Net work seamlessly with zero modifications in your projects. You can use UNIX mail, ftp etc… functions from within ASP.Net using wrappers. No fees required!
  6. “PHP generally being extremely flexible as it uses MySQL for database connectivity, which is freely accessible. Same time on the other hand Database compatibility is expensive for ASP, because it require[sic] MS-SQL product[sic] of Microsoft that needs to be acquired.”
    So according to her, PHP is tied to MySQL as ASP[sic] is tied to MS-SQL. Forget the fact that this, once again, is an outright lie, but how is being tied to one database a good thing? PHP is compatible with dozens of databases or, if necessary, you can write your own in persistence framework as I said before. The same goes to ASP.Net using any of the CLR languages.
  7. “PHP codes can run on different platforms like UNIX, Solaris, Linux, and Windows whereas ASP codes are mostly linked with Windows platforms. Though, ASP programs can run on a Linux platform with the help of ASP-Apache installed on the server.”
    She got it halfway right. Once again it’s ASP she’s comparing to PHP. And Apache::ASP is a product with no future as far as I can see, since ASP itself is on the wane while ASP.Net adoption is blazing ahead. As I’ve said before, I’m using Debian, Apache and Mono. All three of which are free and OpenSource.
  8. “Organizations like government firms normally don’t have much stipulated[sic] commercial budgets and looking for required security, they[sic] really helpful[sic] ASP.net.”
    … I don’t even know where to begin with this one. I’ll let you examine the sheer breath and depth of stupidity for yourself. But it’s nice that she finally returned to the topic which is ASP.Net vs PHP.
  9. “At the end, we can make a conclusion that both programming languages have their advantages and disadvantages specific to user requirement.”
    You just spent the the whole article comparing the invented shortfalls of ASP.Net (or ASP depending on the severity of ADHD) to PHP’s invented superiority you idiot!!  All this when both languages have legitimate strong and weak points that could have been discussed instead of wasting time on this nonsense.
  10. “PHP cannot provide e-commerce application development, only for them[sic] ASP.net will be the best choice.”
    ???
    !!!
    !?!?!?



    I… Have… No… Words…

I must cut off the debunking here, as I’m ready to explode and need to go to a corner and meditate or something, before I throw my computer out the window. I’ll let you read the rest of the articles to see how this “Expert” manages to evade logic, create false premises, and weave a seemingly knowledgeable persona.

P.S. After fighting a hard, lifelong, battle against poor spelling and grammar in my own writings, I feel I must thank Joanna for making me feel better about myself in that arena.

Update 9:30am

Their contact page is up and, surprise! It’s not coded for accessibility.

No <fieldset> tags.
No <label> tags.
No access keys.
No “skip navigation” or “skip to content” links
No HTML alternatives for JavaScript only form functions.