CSS3 media queries with dynamic viewport width

I’ve only recently started using CSS3 media queries in order to target mobile devices, but I found it so easy I wondered why I hadn’t got around to it before. Essentially it boils down to something like this inside your CSS file:

@media only screen and (max-width: 480px) {
	body {
		background:#fff;
		margin: 0;
	}
}

Here, the two declarations for body are only applied if the device’s width is 480px or less. But beware before you start relating that to screen resolutions since CSS pixels are an abstract concept which have little relation to the pixel density of the device. This is actually rather handy since we don’t need to panic about super high res phones- generally they’ll have the same/similar CSS pixel count to a lower resolution device- meaning it’s pretty much one size fits all in our declarations. 480px should cover phones in both portrait and landscape. See Quirksmode’s rather interesting article ‘A pixel is not a pixel is not a pixel‘ for more.

While that’s all well and good, it turns out some simple boilerplate HTML is needed in the head of the document to make the layout fit nicely on mobile devices:

<meta name="viewport" content="width=device-width" />

This tells the mobile browser that the viewport should be the same width as that of the device. This avoids it loading up the page with a much bigger viewport- essentially zoomed out. If you’ve designed a simple mobile layout this can deceiving as it just looks like your text is small. But with the magic meta viewport delcaration in the head, all is well!

At least at first glance.

The thing is, the CSS media query above (purposely) excludes tablet sized devices. I wanted to serve up the standard desktop site on tablets since it works better than the overly simplistic mobile version. But here’s the thing: as I set the viewport to the width of the device, part of my desktop site got cut off on tablets. This is understandable since my site IS wider than the device. But, the device is perfectly capable of displaying it nicely if it just zooms out a little. My design is 1024px and allowing for some slight breathing space, a viewport of around 1080px works well. So of course I tried:

<meta name="viewport" content="width=1080" />

Perfect! On tablets at least, but then on a mobile phone my site no longer fitted the device… so it turned out that I needed the viewport to change dependent upon the width of the device. And Quirksmode was able to come to the rescue again by pointing out that changing the viewport with some JavaScript does work on most mobile browsers (bar FireFox).

So I just needed to pop an ID on my meta viewport tag (to make it super easy to find with JS) and this little script in the head of my document:

if (screen.width > 480) {
	var viewport = document.getElementById('viewport');
	viewport.setAttribute('content','width=1080');
}

Which solves the issue. As Firefox has such a tiny share of the tablet browser space, I doubt I’ll lose any sleep over such users having to pan around/zoom out a little (that’s about as graceful as any fallback gets).

However, I was also using Drupal for the project so I needed a way to get this custom code into the header (preferably in the most legit way possible). And the most legit way I found is (drumroll please)… implementing template_preprocess_page in your template.php file as follows:

function template_preprocess_page(&$variables) {
	$element = array(
		'#tag' => 'meta',
		'#attributes' => array(
			'id' => 'viewport',
			'name' => 'viewport',
			'content' => 'width=device-width, initial-scale=1.0'
		)
	);
	drupal_add_html_head($element, "viewport");
	
	drupal_add_js("if (screen.width > 480) {
			var viewport = document.getElementById('viewport');
			viewport.setAttribute('content','width=1080');
		}", 'inline');
}

Replace the template_ prefix with (your theme name)_ to keep up with current Drupal parlance of course. I also plopped for putting initial-scale=1.0 in the meta tag to ensure that the page is initially zoomed correctly.

Reasons why you might not need this:

  • You want to serve your mobile site to phone and tablet users- just increase the max-width in your CSS3 media query accordingly and use a viewport set to the device width
  • You want to serve up one site for mobile users and another for desktop/tablet users. Set the viewport width of your mobile site to device-width and the viewport of the mobile/desktop version to the width of your site. Tablets will obey, desktop browsers don’t care- simple.
Posted in Drupal, JavaScript | Leave a comment

The Semantic Web: semantic when?

W3C Semantic Web Logo

Developers always like to know what’s happening next with the World Wide Web. For a long time, it’s all been about front end presentation- standards like CSS gradients and HTML5 video. While these things are great, they are far from a step change. Then we have the broad classifications such as ‘Web 2.0′- loosely defined as being about collaboration, standards and the user experience. Blogs and Wikis are often associated with the Web 2.0, heralding the rise of user generated content.

So what really is next now? Well we have HTML5, but that’s all about improving the HTML standard, providing tags for content such as video along with some new APIs for JavaScript. This sort of incremental front-end standards change is what a lot of people consider the evolution of the web to be all about. What’s that? They added a header tag? Great! Now that’s one less div on the page. These changes are nice, but I find the term HTML5 often gets abused. ‘Why didn’t they just make a HTML5 app?’ ‘Wow this is a cool HTML5 site!’ ‘we don’t need Flash now there is HTML5!’. It’s just an evolution of then standard. Much of what can be done with HTML5 can be done in the previous version of the standard, HTML4. Okay so they added a video tag. But that’s all the standard did, defined the standard syntax for the tag (among others). It’s up to browsers to handle the video- which is why we saw the big disagreement on what video codec to use. Having said this, front end standards are of course, still important. But they don’t really advance the web as whole, the general concept remains the same.

The world of the front-end Web is getting better, JavaScript performance has sky-rocketed, standards support has improved dramatically and now web apps can challenge their desktop counterparts. But then there is the Web 3.0, a shift in a very different direction. The curious web developer may be very disappointed when they Google Web 3.0. It’s not shiny, it’s not even that well defined and for the most part it remains a pipe dream.

So what is the Web 3.0? Known as the Semantic Web, it’s all about creating a web which can be understood by machines. Semantics is all about meaning. The current web is overflowing with information but that information is all targeted at humans. To a machine the web is a flat and boring place- reams of text, images and other media none of which share any kind of relation. Of course clever algorithms can dig into that content and make inferences based upon the language used- this is what search engines do- but there are big limitations. A question as simple as ‘give me the details about the person who is described on this page’ is complicated with the current web. A scraper has to go over the page, digging into the syntax put there so it would look nice trying to find words which imply a person is being described. The same for a question like ‘what is the price of the product on this web page?’. Because of this, websites are like silos- data goes in but it’s hard to get out and sharing data between sites is a pain.

The Semantic Web gives us the RDF standard- the Resource Description Framework. A standard for describing resources in a explicit, machine understandable way. Using RDF, we can provide a description of data on a website in a format which other websites can consume and process.

But before we talk details- it’s time to sell the vision. You have some ingredients and you’d like to search the web for recipes which use them but do not contain nuts. This is basically impossible to do today, it’s a very manual process at best. ‘Ah ha’, you say, I can put the ingredients into Google followed by ‘-nuts’. Uh huh, but Google doesn’t really know what you mean, it’s doing a textual search. You just filtered out every recipe page that says something like ‘this recipe contains no nuts’. And Google will have a hard time determining if a page is actually a recipe or just mentions a bunch of foodstuffs. And it’s really going to struggle when it tries to work out if an ingredient is in a particular recipe.

Enter the semantic web. You pose your query via a semantic r engine, it finds recipes from various sites all of which match what you want. ‘hmm’ you think, ‘I have this wine, I wonder which recipes would go best with it?’. So you ask and are provided with a list of recipes which go best with the wine you stated. And just like that the Semantic Web has saved the day.

So how does it work? Well the example above made the SW sound mighty clever. It isn’t. It is just a way to describe the data in an explicit, agreed way. That is, it is all about building up a machine readable web of data. Like the existing web, but just for machines. An extra, better defined dimension if you like. Applications can then do clever stuff with the data. Just as a database isn’t intelligent but a website which uses the database may do some very clever things with the data. Does the database understand the meaning of the data it holds? No. Does the website that uses it? No. Then who does? The person who made the website of course, they made it do clever things with the data even though, the website naturally has no idea what it is is doing. Why do I make this distinction? Because the idea of a ‘machine understandable web’ may make one think that machines will understand what it means for something to be, for example, a person. That’s highly advanced artificial intelligence. To the semantic web, it’s all just a bunch of text but luckily us clever developers can apply meaning to that and manipulate in such a way that cool things happen. Even cooler than PowerPoint slide transitions.

So how do we make this web of data work? If you consider a database, primary keys are used to identify records uniquely. Well, with the Semantic Web, the entire web is our database- so picking non-conflicting keys would be very hard. Enter URIs -Uniform Resource Identifiers. A resource on the Semantic Web is identified uniquely using a URI. A URI is just like a URL except it does not need to dereference to a web page. That is, a URI looks exactly like a URL except when you type it into your browser it may (probably) go nowhere. So I may have a RDF description of myself identified uniquely by the URI ‘http://ashleyfaulkner.co.uk/ashley’. Now whenever someone talks about me on the web, they can provide some RDF with that as the subject. In fact the semantic web is all about triples: subject, predicate and object. So I may say:

http://ashleyfaulkner.co.uk/ashley   a   Person
http://ashleyfaulkner.co.uk/ashley   name   Ashley Faulkner

So here I’ve used the URI ‘http://ashleyfaulkner.co.uk/ashley’ as the key to myself. Yes, the actual me. This is why you would expect the URI to go nowhere when put into a browser. Browsers retrieve resources and in this case I am the resource. Short of teleportation being invented, I’m not going to show up when you type that in. If the URI actually went anywhere (didn’t 404) then the assumption is that the resource that turns up is what is being described. So if that URI went to a page about me, then I’d have just said that the page is a person and is called Ashley Faulkner.

But enough with going all Alice in Wonderland, where do I put that data and how? Well, I could embed it in a web page using RDFa. This standard allows RDF to be put into HTML pages. Or I could provide it as a file formatted in one of the many RDF serialisations. Yes, it gets a bit more complicated because RDF is an abstract language with no concrete syntax- there are various ways you may express RDF but one of the most popular is XML. So I could put the above into an RDF/XML file and have it accessible via a URL. Then an application can come along, read the file and say ‘aha! Ashley is a Person with name Ashley Faulkner’.

Which brings us to another problem. What is a person? In fact, what does ‘name’ mean anyway? If we are being completely explicit we need unique keys for these too. So, I use a URI for the predicate and object too:

http://ashleyfaulkner.co.uk/ashley   http://www.w3.org/1999/02/22-rdf-syntax-ns#type   http://ashleyfaulkner.co.uk/Person
http://ashleyfaulkner.co.uk/ashley   http://ashleyfaulkner.co.uk#name   Ashley Faulkner

Notice I use the URI for RDF type- as this is an URI with an agreed meaning, anyone anywhere knows that this means: ‘The subject is an instance of a class.’- the W3C said so. So now somebody wants to get some information from my site, how do they do so? Well they need to know what they are looking for. Specifically if they are looking for descriptions of people on my site, they need to know I arbitrarily decided that ‘http://ashleyfaulkner.co.uk/Person’ means a human being in the usual sense. How will they know that? They either manually looked into what URIs I use or they are stuck.

What is needed is an agreed URI for people and names so that when a machine wants to find out my name, it just asks for it using the standard URI and the application can then assume that it means the same as any other use of that URI on any other site. And this is the issue, people need to come together and agree on what URIs to use for particular resources. Well as it happens in this instance, the generally agreed URI for people comes courtesy of the Friend of a Friend project. So I can say I’m a person using the following triple:

http://ashleyfaulkner.co.uk/ashley   http://www.w3.org/1999/02/22-rdf-syntax-ns#type   http://xmlns.com/foaf/0.1/Person

The issue is that agreeing upon common meanings and URIs for the vast array of resources people may want to describe is a big task. There has to be a critical mass- we need more people to start publishing data using semantic web standards. RDF, in Web terms  is ‘old’ but it still hasn’t seen widespread adoption. The technologies and standards are  there, but the use simply isn’t- not on any large scale. Thus it may still be a very long time before we see the Semantic web reach its potential- if it ever does.

Posted in Semantic Web | Tagged , , | Leave a comment

Unity divides opinion

Ubuntu Unity LogoUnity replaces the once default Gnome experience in Ubuntu, but is it a step back for usability?

I have used Ubuntu for many years now, in fact I just dusted off the official 6.06 CD I had lying around. I have to admit however that I have always dual booted with Windows. The latest release of Ubuntu saw a switch to the new Unity desktop environment. This had been present on the netbook edition but after some fairly extensive changes they saw it fit for use on its desktop counterpart. I have to say I followed the development of unity with a lot of scepticism, not only did I find it unattractive but the overall usability seemed to leave a lot of be desired. Nevertheless on the release day I took the plunge and ‘upgraded’ to 11.04. I wish I hadn’t.

You have to praise Canonical for wanting to move the desktop forward and enhance the way users interact with their computer rather than sticking with the status quo. The problem is, Unity in its current form feels like a ‘throw everything together and see what sticks’ approach. As part of this we see the introduction of Mac-esque global menus. These appear at the top of the screen for the currently active application. On the plus side, yes they do follow Fitt’s law: throw your mouse to the top and you’ll hit the menu. Additionally, you gain a little extra space. With screen resolutions ever going up though do we really need to eek out every last pixel of screen real estate at the cost of usability? On the negative side, there are a whole lot of drawbacks:

  • The menu only appears when you hover over the window’s title. Not only does this make its discoverability for new users somewhat shocking but it also makes it harder to aim for a particular menu. Say you want the ‘help’ menu which is generally at the end of the list. Typically you could aim straight for it. Now you have to guess where it will appear and adjust accordingly once the menu is revealed. Visibility is a core concept of usability, unity simply ignores this.
  • The menu won’t always fit in the space, especially on small screen devices. The irony here is that Unity, having come from the netbook environment, is designed to work well with small screens. However, that’s now exactly the environment where it’s most likely to fail. At the moment, when the menu overflows there’s no way to access the missing items. The planned fix is to have the extra items overflow into a special extra menu. Not only will this make accessing menus inconsistent (some accessible in one step, others taking two) it also means the supposed advantage of following Fitt’s law is reduced.
  • Some applications don’t yet use the menu. Admittedly it’s early days but it seems a headache that many applications still need to be updated to make this work. Indeed it’s inevitable that some legacy applications never will be updated. It doesn’t take a genius to see that this breaks the golden rule of keeping interfaces consistent.
  • The ‘close window’ button is right next to the ‘Ubuntu button’ when the window is maximised, making accidently clicking it more likely.

Maybe if it was just the problems with the menu I could forgive them. After all, applications these days tend to minimise the need to dig into the menu with any regularity. When you start to use the so-called lenses however, you realise the issues continue. The search functionality is a great feature, something I missed from Ubuntu after having it in the Windows start menu since Vista. It’s presence here is something I’m sure all users will welcome. The problem, at least for me, is the extra effort to manually locate applications. The classic menu did a great job of organising the applications into a logical category-based list. On the contrary, here you now have a massive list of all the applications. There is a drop-down list to filter them by type (which may I add, looks totally out of place) but it just seems to take longer. Scanning down a vertical list of applications was a breeze, now the grid layout often stalls me.

Now I come to the least welcome feature of the applications list: applications available for download. This is a feature which I have seen many others complain about also. When you’re looking for an application on your computer, do you really want a list of suggested apps with poor, out-dated logos and of questionable relevance? The worst part for me is that they waste space that could otherwise be showing more of your own apps.

The release also sees the introduction of overlay scroll bars. A bold move, you may say, as normal scroll bars have served us well for decades. A bold move it is indeed and a move which is hard to adjust to. I’ll give that they take up less space and are slightly neater. They are also, however, harder to use. You have to hover over just to show the controls and then they only perform the page up and down actions. Huh? Where’s the one line at a time scroll? This simply not possible with the new scroll bars, you either jump in massive chunks, un-intuitively drag the whole thing to move like dragging a normal scroll bar or revert back to your keyboard. Again the same problem with the global menus also arises; some applications do not currently use them. The gaps in implementation are obvious. Sure developers will catch up, sure this will encourage them to but in the meantime end users suffer.

I do understand that it’s early days, which is no doubt why there’s almost no way to customise Unity out of the box. Want to change the way the launcher hides? Sorry no can do unless you install Compiz settings manager. Even then, there’s few preferences you can set. In future no doubt a proper preferences area for Unity will be introduced, along with various fixes and improvements. It just feels like Unity came along one iteration too early, it simply isn’t finished. Sure, throw it out there and see what people think but just remember this is what new users are going to see too. The existing community may be forgiving about certain issues but to a new user that IS Ubuntu. If something doesn’t work or there are inconsistencies, they will think that’s just how Ubuntu always is.

Suffice to say, Unity and I don’t really get on. The reaction on various blogs suggests other users have similar reservations too. I’ve now moved to Kubuntu, it’s been many years since I’ve used KDE but it’s come along very well. It’s slick, full featured and customisable to boot. Some may say Kubuntu isn’t the best KDE distribution, but it leaves KDE pretty much untouched which I personally like. Also, because it’s Ubuntu under the hood, the unbeatable community support is easy to come by. I’ll check back on Ubuntu when the (hopefully more polished) 11.10 arrives.

Posted in Linux | Tagged , , , | Leave a comment

Simple Drupal 7 Galleries

Viewing the gallery

Viewing the gallery

An issue I faced quite early on with Drupal 7 was how to add a gallery to pages. Many of the modules are lagging behind with their support for the latest version or are overkill. The Gallery plugin, for example, doesn’t currently support Drupal 7 and is probably too much for those wanting the odd gallery on their website.

The good news is that Drupal 7 includes the Content Construction Kit (CCK), ImageField and ImageCache as part of its core, so making a gallery turns out to be relatively simple. The only additional module I made use of was ColorBox, so be sure to install and enable that before following the steps below. It’s currently in beta for 7.x but working well.

Before starting though, it’s important to know the limitations of this approach. Firstly you can only use one upload location per content type so you could end up with a very big unorganised folder if one content type has a lot of images associated with it. I tried to negate this as much as possible through the use of custom content types depending on the information the page was displaying. This is good practice anyway, but it goes without saying not to force this and have a lot of meaningless or very similar types just to get a different upload location. Certainly don’t create a content type for a particular instance of an entity. You will also only be able to upload one image at a time, so you may find this too tedious if you have a lot of images.

The first step is to create a content type with an ImageField. To do this, go to ‘structure’ in the menu and then ‘content types’. Click ‘add a content type’ and after naming and applying any other desired settings (such as default parent in the menu), choose to save and add fields.

Add a field with type ‘Image’, using the ‘Image widget’. Give it a sensible label and name then click ‘save’. You’ll probably want to specify ‘public’ as the upload location now, but you may choose to use a private location to control access to the images.  Although you may control access to the gallery page, remember that using a public location will mean that anyone could navigate directly to the images.

Editing the gallery

Editing the gallery

Continue and specify a file directory. This directory will be created inside your public or private files directory, depending on the location you specified previously. If desired, you can also specify maximum and minimum allowed resolutions. If you want to add captions to your image, choose ‘Enable title field’. For the preview type, you’ll probably want to use thumbnail, it’s just the type of image you’ll see while editing, not what visitors will see. The number of values option is the most important; it will allow you to specify multiple images per single node. Generally you’ll want ‘unlimited’. Once you’ve saved the field you’ll be back at the field listing where you can add more fields if desired. Click the ‘manage display’ tab and for your image field, change the format to ColorBox. Then click the ‘cog’ (edit) icon at the end of  the row and set the image style to ‘thumbnail’ and the link as ‘to file’. Click ‘update’ and then ‘save’.

When you go to create an instance of your new content type you’ll be able to upload multiple images which will then be displayed one after the other on the page. Adding some simple CSS to your theme can make the display of this more appealing. The CSS class to use will depend on what you called the field. For example, for a field named ‘gallery’ the following CSS will place images horizontally with a border which changes on hover:

.field-name-field-gallery img {
    border: 2px solid #CCC;
    float: left;
    margin-bottom: 5px;
    margin-right: 5px;
}
.field-name-field-gallery img:hover {
    border: 2px solid #000;
}

By specifying a title, ColorBox will use this in its caption area. Once you have saved and view the page, you should note that clicking the image shows a ColorBox overlay which lets you view the image at full size and also easily navigate between images.

Finally, you can customise the way ColorBox works at Configuration, Media, ColorBox. In particular I chose ‘Per field gallery’, this depends somewhat on preference and only comes into play if you decide to add multiple ‘galleries’ to a page. You can change how your thumbnails look at Configuration, Media, Image Styles. The clever thing about this is that you can change the thumbnail style even after you’ve uploaded images; the existing thumbnails will automatically update to fit the new style.

That completes the components of simple ImageField based gallery; no doubt there’ll be more options once modules start to catch up with Drupal 7.

Posted in Drupal | Tagged | 4 Comments

Web apps vs. the world of operating systems

With the ever increasing development of cloud computing, are web applications a threat to traditional desktop operating systems?

Cloud computing is a term thrown around a lot these days and increasingly we are seeing a shift to the cloud. The cloud offers many advantages, key among them being that data is stored in a remote (hopefully) secure location. The use of parentheses there is a not-so-subtle hint about one of the major issues that comes along for the ride.

One of the biggest questions is what do web apps mean for operating systems, are we going to see Windows and Mac turn into fancy, somewhat superfluous ways to launch a browser? It seems Google thinks so as it has been busy creating Chrome OS, an operating system which is, in essence, just a browser. It’s almost laughably simple, they’ve taken Ubuntu, stripped it down and made it run only a subtly different version of Chrome to what we already see today.

With Internet Explorer 9, Microsoft has worked to integrate web applications into the operating system so that line between native and web becomes somewhat blurred. One of the main changes has been the size of the browser ‘chrome’, as with many of it’s competitors Microsoft has decided to put its browser on a diet. This is of course an ever increasing trend, giving as much screen real estate to the web-pages themselves as possible. Indeed, making the UI fade into the background has become a bit of a bandwagon with phrases such as ‘so I get to see the content I care about’ being thrown around in abundance. Let’s not pretend for one minute that all but the most casual of users can afford to throw away the rest of the OS and just use the browser though.

Some applications simply have no net based alternatives, some aren’t feasible because of the sandbox created (very purposefully) to stop web-pages breaking out into the rest of the OS.

It’s not just technical feasibility that’s an issue though. Let’s not forget that web apps are a bit of a user interface nightmare. Native apps at least integrate with the look and feel of the operating system and generally conform to guidelines, at least partially through the constraints of the API. This means users of a Windows computer have a reasonably constrained, consistent environment where user’s mental models aren’t consistently challenged and broken. The same goes for Mac. Yes, it’s not perfect but native apps are generally more in-keeping and produce a better overall user experience. This is why we constantly see the ‘applification’ of common Internet sites for mobile devices. It produces a user experience which is more integrative with the environment of the rest of the phone. Perhaps Apple is partly to blame here, a sucker for great user experience and the benefits of integrated hardware and software, they promoted the app-centric model heavily with the iPhone.

So can you create great web apps for the iPhone which are as good as native equivalents? Yes, but there are at least three issues. Creating a native application restricts and encourages developers to follow certain patterns. Most of the UI will be made out of standard elements provided in the SDK. The end result is with little effort the application looks like an iPhone app. When creating a mobile web app you ideally want it work on all platforms which have a reasonable browser. The natural tendency is that it has it’s own style, this is great in that it’s consistent across platforms but not so great in that it looks nothing like the rest of the UI the user is exposed to on the phone. You could make it look like an iPhone app by carefully hacking the UI (that is trying to replicate the iPhone UI using HTML elements and images) but that’s not great for users on other platforms. Besides, with different people trying to hack together iPhone style UI’s you get different results which in the end could be less consistent and create more confusion than if they hadn’t bothered at all.

Problem number two is simply that on mobile devices users are now accustomed to the app-centric model, specifically in terms of acquiring and running. People go the the app store on their phone to find new apps, they don’t go off searching the Internet hoping they’ll find a web-page which just happens to work perfectly on their phone and meet their UI expectations. It’s with fairly good reason too because bar the most popular sites (Facebook et. al.) it’d be largely a crazy thing to expect.

Problem three is that currently web apps can’t be used offline on mobile devices. Nobody can seriously pretend that the network is always going to be a. available b. fast and c. that the users won’t mind all that data usage.

The problem is, if web applications are to become dominant, how do we avoid having to create different versions for different platforms? The only way to get around this is to forget about device and platform specific interfaces and put the web at the forefront. Getting web apps to conform to any sort of guidelines, however, is nigh on impossible. In addition to this, hands up which OS makers, mobile or otherwise would like their platform to be a glorified browser launcher with no unique selling points or room for innovation other than to update the engine which the user never sees. How could Microsoft ever retain it’s customers if the applications everyone cared about were web based and ran the same on any platform? Ultimately it’d only lead to fragmentation of the web as non-standard capabilities get added to differentiate and aid lock-in. Otherwise, the only hope for such big software companies would be to get businesses and users paying for their online services and applications. This is a move which is beginning to happen with Microsoft bringing out services such as Office 365.

What will be most interesting however is how operating systems will change further down the line. Will they embrance the web at the expense of their own identity or will try to keep it locked in its box and offer compelling platform specific applications in the hope users will stay?

Posted in Cloud computing | Leave a comment

PHP SQL injection & other basic security issues

SQL injection is the number one way websites are compromised. This stems from its relative simplicity and the fact that a simple programmer oversight on just one piece of data can leave the entire site vulnerable. Here I run through how to prevent SQL injection attacks and some other basic security issues using the idea of a simple login form.

The theory behind SQL injection is simple to explain. SQL queries are generally built up as strings which are then run on the database. A natural operation to perform is to get some user provided data e.g. username and password and put this into the query. An example query, not populated with provided data would be:

SELECT * from users WHERE username='' AND password= '';

This all seems simple, we just place the user provided details in the correct place. E.g. if the user provides the username user123 and the password secret, our query looks like so:

SELECT * from users WHERE username='user123'
AND password ='secret';

When we run this, if the user is in the database we’ll get a result, else we’ll get back nothing. We could have opted to just return the number of results using COUNT but we’ll assume we’re going to count the results ourselves.

Here’s an example PHP implementation of this:

if(isset($_POST["submit"])) {
    $query = "SELECT * from users 
WHERE username='" . $_POST["username"] . "' AND password='" . $_POST['password'] . "';";   
    
    if(mysql_num_rows(mysql_query($query)) > 0) {
        echo "Logged in!";
    } else {
        echo "Incorrect username or password";
    }
}

So here we’ve taken the POST values from a user form submission, placed them in our query and ran it. This code however, is very vulnerable. Lets imagine the user enters any username and their password as this:

‘ OR 1 =’1

Now when we place this into our query, it becomes:

SELECT * from users WHERE username='' AND password='' 
OR 1 = '1';

Our query will now return results even if the login details are completely incorrect. The user has ‘broke out’ of the password value and modified the criteria of the query. The first criteria can fail but 1 will always equal 1, so we’ll always get results.

The code was wrong for two resons, first it failed to escape the user input, it allowed the use of an inverted comma to break out into the logic of the query. Second, our criteria for success is whether there is more than one result. Thus if 100 results get returned, we let them in. We should have checked that we got one and only one result and disallow the login otherwise. If we made this modification then the attack above would not work (if we had more than one user in the database).

To stop user input breaking out into the logic of the query, PHP provides the mysql_real_escape_string function. This needs to be run on each piece of user provided data we put into the query. The improved code is thus:

if(isset($_POST["submit"])) {
    $username = mysql_real_escape_string($_POST["username"]);
    $password = mysql_real_escape_string($_POST["password"]);

    $query = "SELECT * from users 
WHERE username='$username' AND password='$password';";   
    
    if(mysql_num_rows(mysql_query($query)) == 1) {
        echo "Logged in!";
    } else {
        echo "Incorrect username or password";
    }
}

The user can no longer break out of our query as when they try, their inverted comma will be escaped, that is a backslash character will be placed before it.

Unfortunately in PHP there’s one more factor we need to consider, though it’s becoming less of an issue. PHP has a deprecated (as of v5.3.0) feature call magic quotes which automatically escapes incoming data. Whether or not this is on depends on the server configuration. If it is on and we use the above code, we’ll end up with data which has been escaped twice.

So we first need to check if the data has been escaped. We can do this using the function get_magic_quotes_gpc(). It’s best to make a simple function for escaping data which checks this. For example:

function escape_data($data) {
    if(get_magic_quotes_gpc()) {
       return $data;
    }
    return mysql_real_escape_string($data);
}

First the function checks if magic_quotes are enabled. If so, the data will have been escaped already so it’s returned as-is. Else, mysql_real_escape_string is used to escape the data before returning.

Revisiting the example, here’s the final code which makes use of the above function:

if(isset($_POST["submit"])) {
    $username = escape_data($_POST["username"]);
    $password = escape_data($_POST["password"]);

    $query = "SELECT * from users 
WHERE username='$username' AND password='$password';";   
    
    if(mysql_num_rows(mysql_query($query)) == 1) {
        echo "Logged in!";
    } else {
        echo "Incorrect username or password";
    }
}

Now comes the obvious question, if I have magic_quotes on, why bother with the function? Can’t we just assume all data is escaped? The reason is this creates a big portability problem. If we move the code to a different server with magic quotes off, suddenly all the user inputs go unchecked. Because we never put the escape function in, we have to go manually looking for all user provided data and escape it. If you have magic_quotes on and you can configure the PHP settings (that is, you are not on shared hosting with no configuration access) you should turn it off and escape data manually as needed. For portability it’s best to always make the PHP application responsible for escaping user provided data. There are a couple of other good reasons for disabling magic quotes too:

  1. As it escapes all data there is a performance hit when it escapes data that doesn’t need to be
  2. Having all data escaped can be annoying as not all user input is destined for a SQL query, e.g. it could be sent out in an email. Also, you may want to perform some manipulation on the raw data before it gets escaped

Above I have only mentioned the practice of modifying a query. We could also consider that the user may try to put in a totally different query, e.g. they could enter their password as:

‘;DELETE FROM users WHERE username=” OR 1=’1

Here the select query has been ended and a whole new delete one added. Luckily, PHP disallows multiple queries so the above, even though it produces a normally valid SQL query will produce an error when it is run. The escaping technique would also prevent this anyway. That’s not to say a user couldn’t easily clear an entire table with one SQL injection though. Consider that there is functionality provided for a user to delete, for instance, a post which they have written. They could modify the criteria just as in the SELECT example. This is where limits are useful in providing an extra barrier of defence. If you know a query should only affect a set number of database entries, specify it explicitly:

DELETE FROM posts WHERE post_id = '1' LIMIT 1;

Now if the user manages to provide a post_id of, as mentioned before ‘ OR 1 =’1 they will have managed to only delete one post- a far cry from destroying all data in the table.

It’s important to remember that other systems (besides standard PHP) allow multiple queries, making the need for escaping data of even more importance.

One final and important consideration is the rights that the user accessing the database has. When you provide your database access details through PHP, you specify a username and password. You should ensure that the website uses a MySQL user account with limited privillidges. To do this, you should create an account and assign privillidges accordingly by following the MySQL documentation. Many web hosts also provide a simple way to do this through the sites control panel. Allowing only select, insert, update and delete should be enough. These only modify the data in the tables as opposed to statements such as create, alter and drop which modify the structure of the database.

The example below is a small one page site which is vulnerable to the attacks mentioned above. The simplist way to get it running is using xampp. Install or extract xampp, place the extracted folder (named insecure) in xampp/htdocs then run xampp_start and visit http://localhost/insecure. If you have changed the MySQL login settings, this will need to be reflected in db.inc.

Insecure login example

What security problems does it show?

  1. The database connection file, with the database connection information is stored in a .inc file. This works fine as when it gets included, PHP evaluates it. However if the user visits the file directly (http://localhost/insecure/db.inc) PHP does not evaluate it as it does not evaluate files with the .inc extension by default. This means anyone can see all our database login information. This file should have had a PHP extension and ideally been placed outside the web folder so it could not be accessed via a URL.
  2. The form does not specify whether it’s a GET or POST operation, so the browser defaults to GET. The PHP script handles this by looking in the GET superglobal rather than POST. However, the use of GET means the password is sent in the URL so it’s easy for anyone watching to see it and the URL including the password will be stored in the user’s history. POST should be used instead.
  3. All error reporting is enabled. Aside from being horrible to look at, the error messages can give an attacker hints about the effect their attacks are having, speeding up the process of compromising the site. Error reporting should be disabled for the live site.
  4. The user data is never escaped, so they can inject into the query. It should be escaped as described above.
  5. The code checks if more than zero results have been returned as the criteria for a successful login. It should check if one and only one result has been returned.
  6. SQL query errors are reported, making the process of finding a successful injection much easier. These errors should never be shown to the user, an ambiguous ‘Login failed’ style message is enough.
  7. The root MySQL account is used, and it has no password. This is handy for the example but very dangerous if it were to be used in practice.

Most of the above are fairly common sense but it’s interesting to see just how many security issues even a tiny site can raise.

Posted in Security | Leave a comment

Twitter fixes dangerous link exploit

Twitter logoTwitter today was hit by a wave of Tweets exploiting a bug in the way it handled URLs. Twitter have just fixed the issue, which previously could be exploited simply by posting a URL of the form:

http://twitter.com/anythinggoeshere#@”onmouseover=”javascript:alert(‘test’);”/

Of course, the above example is harmless but the bug essentially allowed for arbitrary JavaScript to be executed, allowing the poster to do much nastier things. Twitter applications were not vulnerable to the bug.

Cross-site-scripting attacks are a real worry for any major website and protecting aginst them is not an easy task. Malicious code must be filtered out when the user submits any text. The most basic attacks are easy to prevent e.g. attempting to submit:

<script type="text/javascript">alert("hello");</script>

This can be prevented simply by escaping the HTML tags or recognising the script tag and removing or disallowing it. More complex attacks may use different character encodings to fool filters or place scripts inside tags users are allowed to use. In the Twitter case, it seems the exploit focused on the way in which it handled internal URLs. Some relatively simple filtering could have seemingly prevented this issue ever arising.

Posted in JavaScript | Tagged , , , | Leave a comment

Benchmarking the latest browsers

As the web has evolved it’s become more and more important for browsers to be able to quickly handle any JavaScript which is thrown at them. Along with this have come various benchmarks, online tests which run intense JavaScript tasks to test performance. Recently Mozilla announced their own browser benchmark, Kraken. With beta versions of Firefox and Internet Explorer around, it seemed a good time to put them through their paces and see how they stack up.

I ran the Kraken and SunSpider benchmarks in the latest releases of Firefox, Chrome, Opera and Safari. I also ran them in the latest betas of Firefox 4 and Internet Explorer 9.

Kraken results

The Kraken results show Firefox has picked up a clear performance boost in its latest beta, bumping it up to the front of the pack. Meanwhile Internet Explorer 9 seemed to struggle a lot, finishing with a time over four times slower than the latest from Mozilla. Opera’s Carakan JavaScript engine, introduced only in the latest version, continues to impress and takes second place. Safari 5 put in an average performance very close to the performance of Mozilla’s current Firefox offering.

In order of slowest to fastest:

Internet Explorer 9 beta 1 56485.4
Safari 5 19021.0
Firefox 3.6 18897.4
Chrome 6.0 16879.3
Opera 10.6 13754.4
Firefox 4.0 beta 6 12652.4
Kraken benchmark results

Kraken benchmark results

SunSpider results

Thankfully Internet Explorer 9 put in a much more competitive performance with the SunSpider benchmark, beating Safari and both versions of Firefox. Though the latest Firefox offering came very close, with the difference being essentially insignificant. Meanwhile Chrome and Opera put in two very fast performances with Chrome managing to take first place.

In order of slowest to fastest:

Firefox 3.6.10 844.2
Safari 5.0.2 579.4
Firefox 4.0 beta 6 534.6
Internet Explorer 9 beta 1 521.6
Opera 10.60 484.2
Chrome 6.0.472.59 404.2
SunSpider benchmark results

SunSpider benchmark results

Overall the performance boost witnessed in the Firefox 4 beta is promising. There’s clearly something Internet Explorer doesn’t like about the Kraken benchmark as its real world performance and the SunSpider benchmark suggest it’s no slouch. Chrome continues to impress while the much lesser used and talked about Opera proves it’s worth taking notice of.

Posted in Web browsers | Tagged | Leave a comment

The Road to Internet Explorer 9

Internet Explorer 9Internet Explorer enjoys the biggest user base of any browser. The problem is, it gained that user base through its inclusion as the default browser in Windows. Rarely does a user choose to use Internet Explorer based on its own merit. Indeed Internet Explorer has had a history of bringing less to the table than its rivals for some years now. The way in which Microsoft almost abandoned Internet Explorer’s development after Internet Explorer 6 gave its rivals a chance to pick up a piece of the browser share pie. Once Microsoft saw its usage share slip, it naturally came out of hibernation and quickly began to add features such as tabbed browsing. Unfortunately its rivals were way ahead of them, and Microsoft were now playing a game of catch up rather than innovating. The result was Internet Explorer 7, a browser mediocre at best.

Internet Explorer 8 saw a change in the way Microsoft operated. A much stronger empathises was placed on standards support. With this though came a challenge, many websites and indeed intranet sites were made to work with Internet Explorer, quirks, dodgy standards support and all. Introducing a newer version of the browser would surely break many websites. To fight this, Microsoft decided standards should be ‘opt-in’, requiring special mark-up on the website and the default mode to be legacy. Thankfully after a little encouragement from developers (read: outcry) they switched this around and made standards mode opt-out. Along with the improved speed and standards support Microsoft clearly decided they needed to offer something unique. So we also got the little known and little used Suggested Sites, Accelerators and Web Slices. They also took the time to catch up and add features present in other browsers. We got a private browsing mode, inline search and better developer tools. In all, IE 8 was almost the Vista of browsers. It added a lot but it also needed a lot of refinement.

So this brings us to Internet Explorer 9. This time around things are looking even better again. Firstly Microsoft has been releasing ‘platform previews’ on a fairly regular basis, providing developers with a preview of the technologies and performance to come in the final product. Essentially these previews are like Internet Explorer minus the end user interface. It now feels like there’s a much better dialogue between Microsoft and the developers.

Performance has seen a considerable boost, there’s a new JavaScript engine called Chakra, which among other things allows for compiling code on multiple cores. In addition to this, Microsoft has bought to the table what it calls ‘full hardware acceleration’ for graphics. This means all elements on the webpage are rendered by using the systems graphics card directly. All other browsers at the moment lack this, Mozilla has begun to play catch up but they have yet to bring acceleration to all HTML elements. The speed increase, especially where a lot of redrawing is required is considerable. The improvements in standards support are also encouraging, with good CSS3 support and support for the HTML 5 audio and video elements. Microsoft have produced a number of demos to show off the new functionality. In addition to this they have submitted many tests to the W3C for areas including HTML 5 and CSS3. Finally Microsoft seem to be back near the forefront of standards implementation and development.

The new Internet Explorer 9 interface

The new Internet Explorer 9 interface

The beta, released yesterday finally revealed the new user interface and gave us an insight into the direction Microsoft is taking with its browser. The interface is as slim as possible, giving maximum screen real estate to what really matters: the content of the websites you visit. In addition to this there is some welcome integration with the Windows 7 OS. Tabs can now be dragged straight from the tab bar to aero snap. This allows you to quickly pull off a tab and have it take up half the screen on either side, great for comparing two sets of information. In addition to this websites can be pinned to the Windows task bar by simply dragging the tab there. Once this has been done the website acts like a pinned application. The website can even specify a logo and jump list actions. What this does is to blur the line between web based applications and native ones.

Taskbar and Jump List integration

Taskbar and Jump List integration

OS integration is something which will become increasingly important and useful as we see more and more services move to the cloud. It’s an interesting move and one other browsers makers may be hesitant to follow. For a start, most other browsers are cross platform so they can’t rely on the features provided by one particular operating system. Browser makers may also not really want their application to fade into the background and become indiscernible from the OS. However Chrome does not seem to show this fear as it already offers a similar feature, minus the more OS specific integration. In fact the Chrome variant opens a web application window with no interface elements. The current practicality of this is questionable though given than most sites require at least the use of the back and forward buttons every now and again.

Overall Internet Explorer 9 is looking very promising and is definitely moving in the right direction.

You can get the beta over at the Microsoft website.

Posted in Internet Explorer, Web browsers | Leave a comment

Creating a guestbook with CakePHP part 2

In my first post I showed how to setup your development environment, create your tables and use the CakePHP command line utility to bake the models, controllers and views. I’m now going to expand upon this bare bones guestbook by adding in some authentication.

Adding authentication

If you’ve looked at the controllers we baked, you’ll see they extend AppController. AppController is a handy place to put code which will effect all controllers. If your project does not have its own app controller, CakePHP uses its default one. In our case the app controller is a good place to configure our authentication. We’ll start by creating the file ‘app\app_controller.php’. Note we do not place it in ‘app\controllers’. Here’s what we’ll put in that file:

<?php
class AppController extends Controller {
    var $components = array('Auth', 'Session');

    function beforeFilter() {
        $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
        $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
        $this->Auth->loginRedirect = array('controller' => 'posts', 'action' => 'adminindex');
    }
}
?>

Here we’ve declared we will be using the Auth component. This is a useful CakePHP component which will handle the authentication functionality for us.  The beforeFilter() function is called before each controller action, here we setup where our login action will be and where it should redirect to once the user has logged in.

We now need to create the login action on the users controller. Open up ‘app\controllers\users_controller.php’. Inside the UsersController class, add the following functions:

	function login() {
		if ($this->Session->read('Auth.User')) {
			$this->Session->setFlash('You are now logged in');
			$this->redirect('/', null, false);
		}
    }

	function logout() {
		$this->Session->setFlash('You are now logged out');
		$this->redirect($this->Auth->logout());
	}

We now have login and logout actions. Each action in our controller requires a view. When we created our app controller we set the logout view to be the login screen, in the code above we redirect to this when the user logs out. This means we only need to add a view for the login screen.

Create the file ‘app\views\users\login.ctp’ and enter the code below:

<?php
	$session->flash('auth');
	echo $form->create('User', array('action' => 'login'));
	echo $form->inputs(array(
		'username',
		'password'
		));
	echo $form->end('Login');
?>

The first thing we do here is to display any messages we set in the controller. When we refer to $session we are actually referring to an instance of SessionHelper created for us by CakePHP. We then use FormHelper to start the login form. We tell it the model this form is for and the action the form should submit to. We then use the FormHelper inputs function to create our input boxes for username and password. We could go further and define custom labels, but we’ll just stick with the defaults.

You should be able to visit http://localhost/guestbook/users/login. Note that if you now try and visit any page, you will be redirected to login. By default CakePHP authentication locks down all controller actions. However we need to create a user to be able to login. To get around this, add this temporary function to users_controller.php:

	function beforeFilter() {
		$this->Auth->allow('add');
    }

This tells CakePHP to allow anyone to access this action, even if they aren’t logged in. Go ahead and visit http://localhost/guestbook/users/add. Add a user, making sure to keep a note of the details you enter. Now remove the code we just added and go to http://localhost/guestbook/users/login. You should be able to login successfully.

We now have our guestbook protected by authentication, however we have everything protected. We need to loosen the restrictions. The two things we want anyone to be able to do are view the guestbook and add a new post. To do this we simply add the following into the PostsController class found at ‘app\controllers\posts':

	function beforeFilter() {
		$this->Auth->allow('index', 'add');
    }

One final change we need to make is to change the main page. At the moment we’re asked to login when we visit http://localhost/guestbook. We want this URL to go to the list of guestbook posts. There are a number of ways to do this but arguably the simplist is to change routes.php. You can find this file in ‘app\config’.

Replace the line:

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));

With:

Router::connect('/', array('controller' => 'posts', 'action' => 'index', 'home'));

That’s all the functionality complete. Logged in users can do anything and guests can view and add a post to our guestbook. You’ve probably noticed however that it doesn’t look much like a guestbook. Normally a guestbook would have separate admin and public interfaces, here ours looks like one big admin console. Luckily CakePHP has an elegant solution in the form of layouts. At the moment CakePHP is using the default layout file found at ‘cake\libs\view\layouts\default.ctp’. Copy this file to ‘app\views\layouts’. The default layout is quite adequate for the admin area, so we’ll just make some simple modifications.

Changing the layout

First of all, you may have noticed the SQL dump which appears at the bottom of each page, lets tidy things up by removing it.

Open ‘app\views\layouts\default.ctp’, find and remove the line:

<?php echo $this->element('sql_dump'); ?>

There’s also some text which reads “CakePHP: the rapid development php framework” in a couple of places. I’m going to change these to “Guestbook admin console”, except the one in the footer which refers to the CakePHP logo.

That’s our admin view sorted, onto the public view. Create the file public.ctp in the ‘app\views\layouts’ folder. I’m going to base the look of the guestbook on Perhypex:Guest. Here’s what you should put in the public.ctp file:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<?php echo $this->Html->charset(); ?>
	<title>
		Guestbook:
		<?php echo $title_for_layout; ?>
	</title>
	<?php
		echo $this->Html->meta('icon');
		echo $this->Html->css('public');
		echo $scripts_for_layout;
	?>
</head>
<body>
	<div id="container">
		<div id="header">
			My Guestbook
		</div>
		<div id="page_container">
			<?php echo $this->Session->flash(); ?>
			<?php echo $content_for_layout; ?>
		</div>
		<div id="footer">
			Thanks for visiting!
		</div>
	</div>
</body>
</html>

This is a simple layout in which we tell CakePHP to use the public css file. We’ll create that file now. In ‘app\webroot\css’ create the file public.css with the following content:

body {
background-color:#ffffff;
margin:0px;
padding:0px;
font-family:Arial, Helvetica, sans-serif;
color:#000000;
}

#header {
background-color:#990000;
color:#ffffff;
text-align:center;
height:80px;
font-size:30pt;
padding-top:10px;
}

/* Contains all the comments */
#page_container {
width:750px;
margin-right:auto;
margin-left:auto;
}

/* Makes elements float to the left */
.align_left {
float:left;
}

/* Makes elements float to the right */
.align_right {
float:right;
}

/* Contains the view or sign guestbook and return to site options */
.options {
padding:30px 0 30px 0;
}

/* The style for options links */
.options a{
font-size:16pt;
}

/* Box which contains the comment */
.comment {
border:2px solid #990000;
margin:10px 0 10px 0;
}

/* Appears at the top of the comment box with the name and date */
.comment_header {
background-color:#990000;
padding:5px;
font-size:16pt;
color:#ffffff;
overflow:auto;
}

/* contains extra fields such as the location */
.extra_fields {
background-color:#c3c5c3;
color:#000000;
padding:10px;
}

/* Contains the persons actual comment */
.comment_body {
padding:10px;
}

/* Section which contains the page numbers */
.page_number {
text-align:center;
padding:10px;
}

/* Appears at the bottom with a link back to perhypex:guest */
#footer {
text-align:center;
padding:20px;
color:#b82929;
}

/* Global style for the links on the page */
a {
color:#990000;
font-weight:bold;
text-decoration:underline;
}

/* How visited links should change */
a:visited {
font-weight:normal;
}

/* How a link should change when the cursor is over it */
a:hover {
text-decoration:none;
}

/* Style for the form the user fills in */
#comment_form {
margin-top:20px;
}

/* Style for the input labels */
label {
font-weight:bold;
}

/* Style for the area in which the user enters their comment */
textarea {
font-family:Arial, Helvetica, sans-serif;
}

Now we just need to tell the index and add functions to use this layout. Add the following as the first line of the index and add functions inside ‘app\controllers\posts_controller.php':

$this->layout = 'public';

We now just need to customise the add and index action views.

Replace the contents of ‘app\views\posts\add.ctp’ with the following:

<div class="options">
	<span class="align_left">
		<?php echo $this->Html->link(__('View the guestbook', true), array('action' => 'index')); ?>
	</span>
</div>

<div class="posts form">
	<?php echo $this->Form->create('Post');?>
		<fieldset>
			<legend><?php __('Sign the guestbook'); ?></legend>
		<?php
			echo $this->Form->input('name');
			echo $this->Form->input('body', array('type' => 'textarea'));
		?>
		</fieldset>
	<?php echo $this->Form->end(__('Submit', true));?>
</div>

We now need to change the ‘app\views\posts\index.ctp’ file. The current layout of this file will be useful as an admin index, so rename it to adminindex.ctp. Next create the file index.ctp and add the following content:

<div class="posts index">

	<div class="options">
		<span class="align_left">
			<?php echo $this->Html->link(__('Sign the guestbook', true), array('action' => 'add')); ?>
		</span>
		<span class="align_right">
			<?php echo $this->Paginator->sort('Sort by date posted', 'created'); ?>
		</span>
	</div>

	<?php
	foreach ($posts as $post):?>
		<div class="comment">
			<div class="comment_header">
				<span class="align_left"><?php echo $post['Post']['name']; ?></span>
				<span class="align_right"><?php echo date("F dS, Y", strtotime($post['Post']['created'])); ?></span>
			</div>
			<div class="comment_body">
				<?php echo $post['Post']['body']; ?>
			</div>
			<?php
			if($post['Post']['modified'] != $post['Post']['created']) { ?>
				<div class="extra_fields">
					Edited by the guestbook owner on
					<?php
					echo date("F dS, Y", strtotime($post['Post']['modified']));
					?>
				</div>
			<?php
			}
			?>
		</div>
	<?php endforeach; ?>

	<p>
	<?php
	echo $this->Paginator->counter(array(
	'format' => __('Page %page% of %pages%', true)
	));
	?>	</p>

	<div class="paging">
		<?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
	 | 	<?php echo $this->Paginator->numbers();?>
 |
		<?php echo $this->Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?>
	</div>
</div>

Add a few posts and see how it now looks. You may notice one minor problem: the posts are listed from oldest to newest, not exactly how a guestbook works. CakePHP offers a simple way to customise this though. Open up ‘app\controllers\posts_controller.php’. Below

var $name = 'Posts';

add the following:

var $paginate = array(
		'limit' => 10,
		'order' => array(
			'Post.created' => 'desc')
		);

Here we’ve said that we want to view 10 posts per page and by the created date descending.

We’re very nearly done, the only thing we are missing is the action to view the admin index page. Unlike the public page, this page will contain the links to edit and delete posts.

Inside ‘app\controllers\posts_controller.php’, add the following function:

	function adminIndex() {
		$this->Post->recursive = 0;
		$this->set('posts', $this->paginate());
	}

That’s it, our guestbook application is complete!

Viewing the guestbook

Viewing the guestbook

Signing the guestbook

Signing the guestbook

Posted in CakePHP, PHP | Tagged , | 3 Comments