2011

oAuth with the Twitter API in Ruby on Rails without a gem

15th December, 2011 - Posted by david

This post is a follow on from my previous post about the oAuth protocol in general in Ruby. Here I detail how to let a user authenticate themselves via Twitter to your web app, request a list of people they’re following (a GET operation) and how to follow a specific user (a POST operation). As this is a follow-on from an earlier post, the following functions used here can be seen there:

  • params(consumer_key)
  • generate_nonce(size)
  • signature_base_string(method, uri, params)
  • url_encode(string)
  • sign(key, base_string)
  • header(params)
  • request_data(header, base_uri, method, post_data=nil)

The Log-in Process

When you sign up for an API key from Twitter, Yelp etc. you’ll be given a Consumer Key and a Consumer Secret. You may also be given an Access Token and an Access Token secret, but that’s for you logging into your own account. If that’s all you wish to do, you can skip this section.

So, if you want to let a user log in to their Twitter (or whatever) account via your site, you need to get an access token. The process for this is as follows:

  1. You request a unique Request Token from Twitter for your ‘Log-in with Twitter’ button
  2. You use this request token, as well as where on your site you want the user to be re-directed back to after they’re authenticated, to build the URL the button points to
  3. They click the ‘Log-in with Twitter’ button on your site
  4. The user is brought to Twitter where they enter their username and password
  5. Twitter re-directs them back to your site, to the URL you gave them in step 2
  6. Back at your site, you’ll now have an oAuth verifier
  7. This can be used to get the user’s Twitter user info, which has been authenticated by Twitter

Step 1: Getting an oAuth request token for your current session

Twitter’s URL for getting a request token from is https://api.twitter.com/oauth/request_token. The request for the token contains a number of parameters, which are then combined with the URL you’re going to be sending the data to and the method of your request (i.e. GET, POST etc.) to generate what’s called a base signature. At this point you don’t have an access token, so your signing key is simply your consumer secret followed by an ‘&‘. Using the functions I’ve mentioned earlier, this can be done as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
consumer_key = 'abcdefghijklmnop' # Obtainable from your destination site's API admin panel
consumer_secret = 'zyxwvutsrqponm' # As above
callback_url = 'http://www.mysite.com/logged-in'
method = 'POST'
uri = 'https://api.twitter.com/oauth/request_token'
params = params(consumer_key)
params['oauth_callback'] = url_encode(callback_url)
params['oauth_signature'] = url_encode(sign(consumer_secret + '&', signature_base_string(method, uri, params)))
token_data = parse_string(request_data(header(params), uri, method))
auth_token, auth_token_secret = [token_data['oauth_token'], token_data['oauth_token_secret']] # save these values, they'll be used again later

# where parse_string is simply
def parse_string(str)
    ret = {}
    str.split('&').each do |pair|
        key_and_val = pair.split('=')
        ret[key_and_val[0]] = key_and_val[1]
    end
    ret
end

Steps 2-5: Authenticating with Twitter

Once you have your request/access token, the URL to direct your user to is simply:

1
login_url = 'https://api.twitter.com/oauth/authorize?oauth_token='+auth_token

You can use standard <%= link_to 'Login', @login_url %> to generate a HTML anchor in your ERB template or whatever you choose. The user will then be directed to Twitter, where they enter their log-in details and get directed back to your callback URL.

Steps 6-7: Getting the user’s info

When the user is directed back to your site, you’ll be sent an oauth_verifier. This can be obtained via Rails in your callback URL’s corresponding controller, via params[:oauth_verifier]. You need to use this to request their user info, as a final check before they’re fully logged in. This results in a new auth token (or what Twitter calls an access token) and auth token secret, which should replace your previous stored values. It is assumed the code below is stored in a model class, where you need to pass the auth verifier from your controller.

1
2
3
4
5
6
7
method = 'POST'
base_uri = 'https://api.twitter.com/oauth/access_token'
params = params() # not to be confused with params in your controller
params['oauth_verifier'] = auth_verifier # this does come from params in the controller
#auth_token_secret here is from above
params['oauth_signature'] = url_encode(sign(consumer_secret + '&' + auth_token_secret, signature_base_string(method, uri, params)))
data = parse_string(request_data(header(params), base_uri, method))

data will now contain an array with things such as screen_name, user_id etc. of the user who just logged in. It’ll also contain a new oauth_token and oauth_token_secret, which should be saved as they’ll be used again.

Now you have a fully validated user, who has authenticated you to access information on Twitter via their Twitter account. So now, let’s access some of that info.

Getting the people a user is following (a GET request)

The process for all GET requests is pretty similar and roughly follows what we’ve done before. We have our array of standard parameters. To this, each of the GET parameters are passed. We use our access token and consumer key & secret to generate our oAuth signature, make the request and parse the response.

1
2
3
4
5
6
7
8
9
method = 'GET'
uri = 'https://api.twitter.com/1/friends/ids.json'
params = params(consumer_key)
# Add the GET parameters here
params['cursor'] = '-1' # start at the beginning
params['user_id'] = user_id # from 'data' array above
params['oauth_signature'] = url_encode(sign(consumer_secret + '&' + auth_token_secret, signature_base_string(method, uri, params)))
uri += '?cursor=-1&user_id=' + user_id # Add in the GET parameters to the URL
followees = JSON.parse(request_data(header(params), uri, method))

Following a specific user (a POST request)

The process for a POST is pretty similar, the only difference being how you handle the parameters to the request. In the example below, I’m assuming you have the user ID of the person you want to follow and that it’s stored in a variable called followee_user_id.

1
2
3
4
5
6
method = 'POST'
uri = 'https://api.twitter.com/1/friendships/create.json'
params = params(consumer_key)
params['user_id'] = followee_user_id
params['oauth_signature'] = url_encode(sign(consumer_secret + '&' + auth_token_secret, signature_base_string(method, uri, params)))
resp = JSON.parse(request_data(header(params), uri, method, 'user_id='+followee_user_id))

So, assuming that was successful, the user should now be following the person with user ID followee_user_id.

Conclusion

Hopefully this will fill in some of the gaps in Twitter’s documentation. When coding this I found plenty of instances where the documentation would say something like “now sign the key”, without actually telling you how to sign it! Very confusing indeed.

Disclaimer: I obviously wrote all this in proper Ruby on Rails classes and controllers, but have extracted the code out here to be presented in modular form. Thus, what’s here is not fully tested, or even elegant, but there should be enough to figure out what you need to do.

Read more...

oAuth 1.0 in Ruby without a gem

10th December, 2011 - Posted by david

Recently I decided to figure out what the oAuth 1.0 protocol was all about and try to implement it in Ruby, as part of a way a) to practice by Ruby on Rails, b) have a look at the Twitter API and c) use both to get an understanding of how websites let you log in/comment via your Facebook/Twitter/Google etc. account, for potential use in future web projects. Sure there’s an oAuth gem out there, and a Twitter gem and probably a generic login gem (or if not, there’s an idea!) but I thought I’d get more out of the process by coding everything from scratch. So, first up is a generic overview of the oAuth protocol.

Each request will have a method (i.e. GET, POST etc.), a base URL to handle the request at the source site (Twitter, Yelp etc.) and a certain set of parameters. Every request I’ve dealt with has had the same 5 parameters, along with various other ones specific to the request you’re making. So, in Ruby, I’d have something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
consumer_key = 'abcdefghijklmnop' # Obtainable from your destination site's API admin panel
consumer_secret = 'zyxwvutsrqponm' # As above
method = 'GET'
uri = 'https://api.site.com/resource/section.format'
params = params(consumer_key)

# These 5 parameters are common to all calls
def params(consumer_key)
    params = {
        'oauth_consumer_key' => consumer_key, # Your consumer key
        'oauth_nonce' => generate_nonce, # A random string, see below for function
        'oauth_signature_method' => 'HMAC-SHA1', # How you'll be signing (see later)
        'oauth_timestamp' => Time.now.getutc.to_i.to_s, # Timestamp
        'oauth_version' => '1.0' # oAuth version
    }
end

def generate_nonce(size=7)
    Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
end

Next, you’ll need to add in any extra parameters to your params hash, e.g. your access token if you have it, and then combine all the above to generate a base string:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
params['abc'] = 'xyz'
signature_base_string = signature_base_string(method, uri, params)

#where signature_base_string function is:

def signature_base_string(method, uri, params)
    # Join up the parameters into one long URL-safe string of key value pairs
    encoded_params = params.sort.collect{ |k, v| url_encode("#{k}=#{v}") }.join('%26')
    # Join the above with your method and URL-safe destination URL
    method + '&' + url_encode(uri) + '&' + encoded_params
end

# I'm a PHP developer primarily, hence the name of this function!
def url_encode(string)
    CGI::escape(string)
end

Next up, you need to generate a signing key, which is a combination of your consumer secret and your access token for the current session, if you have one at this stage (you may not, if the user still hasn’t logged in yet: in that case, a blank string will suffice). With this signing key, you sign your signature base string to get your oauth signature:

1
2
3
4
5
6
7
8
9
10
access_token ||= '' # if not set, blank string
signing_key = consumer_secret + '&' + access_token
params['oauth_signature'] = url_encode(sign(signing_key, signature_base_string))

# where sign is:
def sign(key, base_string)
    digest = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.digest(digest, key, base_string)
    Base64.encode64(hmac).chomp.gsub(/\n/, '')
end

At this point, you’ve all your info nicely encoded in the oauth_signature using your private consumer secret. So, in a kind of public/private key partnership, you need to give the service your public consumer key, so it can validate the encoding of the oauth_signature at the destination:

1
params['oauth_consumer_key'] = consumer_key # from above

So, you’re nearly ready to make your oAuth request. One final thing: all these parameters need to go into the Authorization line in your HTTP header, which is simply a matter of generating another string, as well as indicating you’re using oAuth:

1
2
3
4
5
6
7
8
9
10
header_string = header(params)

# where header is:
def header(params)
    header = "OAuth "
    params.each do |k, v|
        header += "#{k}="#{v}", "
    end
    header.slice(0..-3) # chop off last ", "
end

So, to make your HTTP request, I wrote a generic function (request_data) that will do either a GET or a POST, make the request and return the response body:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
response = request_data(header_string, uri, method)

# where request_data is
def request_data(header, base_uri, method, post_data=nil)
    url = URI.parse(base_uri)
    http = Net::HTTP.new(url.host, 443) # set to 80 if not using HTTPS
    http.use_ssl = true # ignore if not using HTTPS
    if method == 'POST'
        # post_data here should be your encoded POST string, NOT an array
        resp, data = http.post(url.path, post_data, { 'Authorization' => header })
    else
        resp, data = http.get(url.to_s, { 'Authorization' => header })
    end
resp.body
end

And there you go. You should have a response from the API in whatever format you requested, be it JSON, XML or whatever. In my next post, I’ll explain how to use the above specifically for the Twitter API.

Read more...

Favourite Interview Process

16th September, 2011 - Posted by david

Last month I resigned my post from daft.ie and am about to take up a development position with dotMobi, a leading mobile Internet services company. I had done interviews with several places, while reading plenty of articles on various other companies’ hiring practices. At the end of these couple of months, I came to the conclusion that in all my professional career, Daft had the best hiring process for a developer that I had ever both experienced and read about.

The process

Initial interview

First up, as with all jobs, you submit your resume/CV. This is then screened over by the development manager, who invites adequate candidates to a first-stage interview. This interview consists of him and another developer, basically going through the CV with the candidate, asking some general programming related questions, all just to try and get a better overview of the person, ensuring they pass the “no jerks” policy.

Programming Assignment

This is the main beauty of their hiring process, and is discussed in more details below. Candidates are given an assignment to work on in their own time at home, typically over a weekend. The assignment asks the user build a simple web form, with one text input to take free-text search strings that a user might enter when searching for property. They even give sample queries, such as “2 beds for sale in Dublin”. This string is to be submitted to a PHP script where all the necessary data is to be parsed from the string. Once parsed, a call is to be made to the Daft database via their API in order to get a list of results back. This result set is then displayed to the user. Finally, they need to write-up their code, explaining what’s going on in simple English.

Second Interview

Assuming you do a good job in the assignment, you’re then invited back for a more formal interview with senior management (company of about 60 people), to discuss career aspirations, salary requirements etc.

Why I think this is great

Skills it shows

As mentioned earlier, the key part of the process is the programming assignment. First up, the initial entry form and data submission. Here the user can show their knowledge of web security, CSS, (X)HTML standards and more. While none of these are actual requirements, it’s an easy place to show existing, fundamental web knowledge.

Once the data’s at the server, cleaned and verified, relevant data blocks (e.g. area, for sale/to let, number of bedrooms etc.) need to be parsed from the string. There are a number of different ways to do this and gives the developer plenty of scope for flair. A certain amount of analysis is required here also, mapping the domain of real estate to known keywords and looking for these in the string.

You also need to try and spot any relevant areas (e.g. County Dublin, Galway City etc.) the user may have entered. Without going into too much detail (in case potential candidates are reading this), there’s a key optimisation when doing this via the API and it’s a great way to spot those who have been programming seriously for a few years. Even if a programmer doesn’t implement this optimisation, they should still be aware of the problem it solves; this awareness can come across in their second interview, which is also accepted.

Once you know what the user is searching for, you then need to build a call to Daft’s SOAP API. This is good because it usually requires the candidate install PHP’s SOAP interface. While not overly complicated, it’s not trivial either and shows that the candidate is able to play around on a UNIX machine. Using the API also illustrates a candidates ability to follow documentation and quickly get to grips with a new system. Finally, by building a query from your parsed data that can be understood by the API, the candidate again has a chance to show some good, tidy code.

After pulling data from the API, the candidate again has a chance to show off XHTML/CSS skills to display the results set. Further ‘enhancements’ could be shown by doing all this over AJAX, without a page reload.

Finally, the candidate needs to document their code. This will illustrate communication skills, their attention to detail, writing skills (grammar, spelling) and more. Everyone in the software industry knows that communication is a large part of software development and it’s important to have good communicators on your team. Good code followed by an average write-up can often be less preferable than average code (which is generally easier to improve on) with a good write-up.

Why it’s better than others

While reading about other people’s interview experiences, one thing that stood out as annoying most developers is white-board coding, i.e. where you’re required to write some code on the fly, either on a white board or piece of paper. This a completely unrealistic situation and is asking a programmer to do something way outside their normal environment. Similar to this would be writing a program in the employer’s office in the space of an hour to 90 minutes; again this is wholly unrealistic as programmers are used to their own environment, have their own tools etc. The beauty of the Daft assignment is that it’s too tricky to do in less than two hours, plus they let you do it at home on your own machine. Yes, this does allow for potential plagiarism, but once you’re offered a job, you’re put on 6 months initial probation, so if you don’t code your assignment it’ll soon become obvious and they’ll have scope to not make you permanent.

Another great thing about it is that there’s no generic analytical questions such as “Why are man-holes round?” or “How many ties were sold in New York City last year?”. The value in asking these questions is surely waning these days, as they are easy enough to prepare for and people have learnt to expect them. Good programmers need to be good at analysis, but enough information about a candidates skills can be gleaned from how they approach the assignment.

Finally, there’s no over-bloated 7 round interview process. From the 2 interviews a candidate does, coupled with the assignment, it’s very easy to see what a person is like and that they’ve a decent level of programming competency.

One flaw

Not everything’s perfect and after chatting to my manager about the interview process, in preparation for this blog post, he pointed out that it’s poor at highlighting any “rockstar” programmers, as well as any existing systems administration skills. So, if you have 2 people competing against each other, one a decent programmer, the other amazing, it can be hard to differentiate between the 2; what’ll happen more often than not is that the ‘nicer’ one will be offered the job first.

Conclusion

From the above, it should be easy to see that current interview processes are far too complicated. 2 interviews will always be the least that’s required, but when you throw in a slightly complicated programming assignment, there shouldn’t be need for much more. The assignment’s a good way to test programming competency while the interviews are good to test a person’s likeability and career aspirations.

Read more...

MySQL reached max integer limit

23rd August, 2011 - Posted by david

Whenever anything generates a MySQL error at work, the whole technical team gets an email about it, with full debug info. We recently got one for an area that I look after, so it was up to me to investigate. The title of the error was a slightly cryptic

Error: Duplicate entry ‘0’ for key 1

Clearly this had something to do with the primary key, which was a simple unique integer ID. As this is a pretty large table, I had a feeling that we had reached the upper limit of what could be stored for the field type (MySQL’s MEDIUMINT, signed). Looking at the maximum value for the ID, I saw it was 8388607; according to this table on the MySQL website, this value is the maximum that can be stored in that type of field, so this was clearly the problem. (N.B. an auto-incrementing field shouldn’t ever be defined as signed, as you’ll never go into the negative indices, but that was done before I came along!)

The solution? Surely a simple readjusting of the key to be unsigned, or to further future proofing, changing to the larger INT… That’s what I thought and quickly (in test first, of course!) did a

1
ALTER TABLE table_name CHANGE COLUMN id id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT

This should then change the maximum value allowed in the ID field to 16777215. So, to test this, I went to insert a row, specifying NULL for the primary key value. However, I still got the same error. Doing a DESC on the table told me that the ID field had been changed to MEDIUMINT UNSIGNED correctly, so that wasn’t the issue. After further research I determined that what happend was that MySQL’s internal counter for that auto increment field was still set to 0, due to the rollover caused after reaching the maximum integer value. To overcome this, you need to point the internal counter back to where it should be, i.e. the current maximum value of your ID field, as follows:

1
ALTER TABLE table_name AUTO_INCREMENT = 8388608

So, you set it to a value that’s one greater than the current maximum ID.

Read more...

jQuery Mobile and Dynamic Data

23rd May, 2011 - Posted by david

UPDATE: I’ve recently been asked by entertainment.ie to stop scraping thier licensed data, to which I’ve duly agreed. Thus, the app is no longer live. However, the lessons learned below are still valid. END

Haven’t posted for a while, mainly because I’ve been busy trying to teach myself Ruby on Rails, so haven’t created anything new in my spare time. Have come up with a few interesting fixes/ways to do things in relation to Facebook code in work though, so will hopefully do a post on those in the near future.
In the meantime, I wrote a very small little web application last week using the pretty cool jQuery Mobile framework. The app, called What’s on now (link removed, see UPDATE above), is simply a list of what’s on now and next on Ireland’s 17 basic cable channels. The data is pulled from a similar page on entertainment.ie, with the unnecessary channels filtered out. At the moment the app is pretty simple, but I plan to add to it over time (e.g. to add an option for the non-basic channels), updating this article as I go.

I did this mainly to have a quick go with jQuery Mobile, to see what it could do. I could’ve used PHP and built a mobile-sized HTML page, but it’s always good to try new things! Most of the development was pretty straight forward; however, because the data is retrieved dynamically every time the page is loaded, there’s a couple of tricks you need to apply to get the jQuery Mobile framework to do it’s magic.

The main page you’re greeted with is simply a glorified un-ordered list of programmes, with separator list items to distinguish the channels. I’m not going to go into the details of how you need to structure a page (see links at the end of this post) but here’s a snippet of the HTML:

1
2
3
4
5
6
7
<ul id="channels" data-role="listview">
    <li data-role="list-divider">RTE One</li>
    <li><a href="#RTE-One-1">21:00: 9 O'Clock News</a></li>
    <li><a href="#RTE-One-2">21:35: Prime Time</a></li>
    <li data-role="list-divider">RTE Two</li>
.. etc.
</ul>

When the page loads, ul#channels is empty. The data is called via a jQuery GET, which gets over cross-domain restrictions by using YUI, thanks to the Cross-Domain AJAX mod. The relevant data is filtered out and formatted and each of the li‘s are built and inserted into #channels. At this point, if you look at the page in your browser, it’ll still look like an ordinary list – you need to tell jQuery to work it’s magic on the dynamically created data. In this instance it’s done as follows:

1
$("#channels").listview("refresh");

Once I had my list of programmes, I thought I may as well add the info for each program, seeing as I already had the data at my disposal. The route I decided to go down here was to create new ‘page’ div‘s for each program, each one having it’s own ID, then link to each of these pages from the li‘s. Again, the scope of building one of these pages is beyond this blog post and well documented elsewhere, but here’s a quick sample:

1
2
3
4
5
6
7
8
9
10
11
12
<div data-role="page" id="RTE-One-1" data-url="RTE-One-1">
    <div data-role="header">
        <h1>RTE one</h1>
    </div>
    <div data-role="content">
        <h2>9 O'Clock News: 21:00</h2>
        An update on the latest domestic and international events of the day.
    </div>
    <div data-role="footer">
        © David Coen 2011</div>
    </div>
</div>

This is simply added to the body using $('body').append(page); (where the variable page is a string of HTML such as the above). So, again here you need to tell jQuery Mobile that you’ve added a new page, so it can do it’s magic. This is achieved by the one simple line:

1
$('#RTE-One-1').page();

Hopefully this post will clear up a couple of things for anyone using jQuery Mobile with dynamically generated data. As I promised, here are some links of articles that helped me get a better understanding of the framework:

Full implementation code (UPDATE 2)

I was requested by @danny to post the full source code, seeing as the app is actually no longer available, so I’ve decided to put most of it here. I excluded some of the scrape JS (as indicated in the code comments) to prevent the app being re-used somewhere else.

So, first up, is the initial HTML page, with header and footer blocks, an empty content block and the JS and CSS includes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<title>What's on now</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css">
<script type="text/javascript" src="./jquery.min.js"></script>
<script type="text/javascript" src="./jquery-mobile.min.js"></script>
<script type="text/javascript" src="./whatson.js"></script>
<div data-role="page" id="home">
<div data-role="header">
    <h1>What's on now</h1>
</div>
<div data-role="content">
    <ul id="channels" data-role="listview"></ul>
</div>
<div data-role="footer" style="text-align: center;">
    <a href="http://www.drcoen.com">© David Coen 2011</a></div>
</div>

Next up is the javascript file, whatson.js in the above. Don’t forget, the $.ajax call has been overwritten by the Cross-Domain AJAX plug-in I mentioned earlier. Addtionally, I’ve used a few functions from php.js. to replicate this functionality in JS, namely str_replace, in_array and trim. I’ve excluded them here but they can be found in the php.js website.

Also, just to re-clarify, the page i was scraping had a list of channels and programs that were on now (prog1) and next (prog2).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$(document).ready(function() {
    var cable_channels = [
        'RTE One',
        'RTE Two',
        'BBC One',
        // etc......
    ];
    $.mobile.pageLoading();
    $.ajax({
        url: // URL for the data
        type: 'GET',
        success: function(data) {
            html = $(data['responseText']);
            // for each channel scrapped
            $('td.listing-channel', html).each(function(){
            var channel = // code omitted
            var prog1_start, prog2_start, prog1, prog2, prog1_name, prog2_name;
            // if it's a channel I'm interested in
            if (in_array(channel, cable_channels))
            {
                // get the name, start time and description of the program currently being shown on the channel
                prog1_start = // start time of 1st program - code omitted
                prog1_name = // name of 1st program - code omitted
                prog1 = // description of 1st program - code omitted

                // do the same for the one on after it
                prog2_start = // start time of 2nd program - code omitted
                prog2_name = // name of 2nd program - code omitted
                prog2 = // description of 2nd program - code omitted

                // replace spaces with '-' for a valid #id
                var id = str_replace(' ', '-', channel);

                //create new block on the main page for our channel and it's 2 programs
                var li = '<li data-role="list-divider">' + channel + '</li>' +
                    '<li><a href="#' + id + '-1">' + prog1_start + ': ' + prog1_name + '</a></li>' +
                    '<li><a href="#' + id + '-2">' + prog2_start + ': ' + prog2_name + '</a></li>';
                $('#channels').append(li);

                // create a new page for the program description - clicking on the program in the <li> above will
                // bring you to this new description page
                var page = '<div data-role="page" id="'+id+'-1" data-url="'+id+'-1">' +
                    '<div data-role="header">' +
                        '<h1>' + channel + '</h1>' +
                    '</div>' +
                    '<div data-role="content">' +
                        '<h2>' + prog1_name + ': ' + prog1_start + '</h2>' + prog1 +
                    '</div>' +
                    '<div data-role="footer" style="text-align: center;">' +
                        '<a href="http://www.drcoen.com">© David Coen 2011</a>' +
                    '</div></div>';
                    $('body').append(page);
                    $('#'+id+'-1').page();

                    // Do same again for 2nd program
                    page = '<div data-role="page" id="'+id+'-2" data-url="'+id+'-2">' +
                        '<div data-role="header">' +
                            '<h1>' + channel + '</h1>' +
                        '</div>' +
                        '<div data-role="content">' +
                            '<h2>' + prog2_name + ': ' + prog2_start + '</h2>' + prog2 +
                        '</div>' +
                        '<div data-role="footer" style="text-align: center;">' +
                            '<a href="http://www.drcoen.com">© David Coen 2011</a>' +
                        '</div></div>';
                    $('body').append(page);
                    $('#'+id+'-2').page();
                }
            });
            $("#channels").listview("refresh");
            $.mobile.pageLoading(true); // kill the page loading modal
        }
    });
});

I realise this code could be alot cleaner, but the app was still in it’s early stages before I was asked to take it down, thus I haven’t spent time tidying it up. Hopefully there’s enough here to figure out how to do what you need!

Read more...

Regular expression (regex) to remove double encoding of html entities

30th March, 2011 - Posted by david

When you have users copying and pasting in data to forms on your website, which then gets stored in your database, you invariably end up with all sorts of ways of encoding and storing special characters. Ideally, these will end up in your database as the correct characters (such as € for the euro symbol), which will then get encoded as HTML entities when you display this data on your website (so, € becomes &euro; in the HTML).

However, with older systems, especially those built in-house, you end up with the HTML entity version of certain characters in your database. It’s pretty much a fact of web development. Let’s use the example of a string that says “Price: €100” but gets stored in the database as “Price: &euro;100”. When you go to display this text on your encoded web-page, you end up seeing things such as “Price: &amp;euro;100” in your browser. This is a result of double encoding, as the & in &euro; is first getting encoded as &amp;.

In order to remove these, I came up with the following function, that uses a simple regular expression to tidy such instances up.

1
2
3
4
function remove_double_encoding($in)
{
    return preg_replace('/&([a-zA-Z0-9]{2,7});/', '&$1;', $in);
}

What this does is looks for any 2 to 7 letter strings with &amp; immediately before them and ; immediately after. When it finds a match, it simply replaces the &amp; with &. It does this for all instances in your input string.

Update: Forgot that you can also have purely numeric codes here, so added ‘0-9’ to the regex.

Read more...

Google Maps Zoom Control missing in Internet Explorer (IE): a solution

23rd February, 2011 - Posted by david

When I was working on rebuilding daft.ie’s mapping system for the modern web, it involved writing alot of Javascript (JS) code and loading this via a couple of JS source files, one external one from Google (i.e. the core Google Maps JS) and another from our own web-servers.

It all worked fine in most browsers, except of course Internet Explorer (IE). To be fair, IE8 was fine; however, seemingly at random, the zoom control would sometimes not appear on the map for IE7 and IE6 and you’d get one of IE’s very unhelpful error messages. At Daft we like to ensure that the site is available and consistent on all browsers, so it was imperative to get this issue sorted.

Because of the aforementioned unhelpful error messages, it proved to be a very difficult bug to nail down. I began to suspect that it was due to the code from Google loading after the map is set-up in the daft.ie hosted file, i.e. IE was calling Google-specific functions before it had the code. As it was all wrapped in a jQuery $(document).ready I didn’t think this could happen, but turns out that in the older versions of IE it could! I tried moving the JS around, loading the Daft-specific JS as late as possible, to try and give the Google JS the maximum amount of time to load, but that was to no avail.

So, I needed to find a way to load the Daft JS when I was 100% certain the Google JS had arrived. After various futile attempts, the solution ended up being pretty simple – change my

1
$(document).ready(function() {

to

1
$(window).bind("load", function() {

That way, the bulk of my JS is only executed once everything else has loaded and sure enough, the issue disappeared!

Read more...

Planned site downtime: how to handle it with PHP

25th January, 2011 - Posted by david

Today I read an interesting article on Google’s Webmaster Central blog about planned down time, for maintenance or whatever. Basically, you need to communicate to users and potential site-crawlers/indexers that the site is down, but it’s ok, it’ll be back up soon!

My suggestion would be to do a mod-rewrite (if using Apache, but other web servers have equivalents) on all incoming files to your 503 file, something like

RewriteRule ^(.*)$ 503.php [L]

If you put this rule first, you can leave the rest of your rules where they are as the “L” at the end of the line tells Apache that this is the last rule it needs to process, i.e. it can ignore the others.

Then, in your 503.php you need to tell any web-crawlers/site-indexers that the service is down but will be back at a certain time using PHP’s header function, as well as telling your users the same using a plain simple descriptive paragraph:

<?php

$back = 'Tue, 1 Feb 2011 12:00:00 GMT';

// tell any web-crawlers
header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Retry-After: '.$back);

// calculate time to wait for users (I assume here it'll only be a matter of hours)
$vals = array('hour' =--> 3600, 'minute' => 60, 'second' => 1);
$back_in_seconds = strtotime($back) - time();
$back_text = '';
foreach ($vals as $key => $time) {
    $cur_time = floor($back_in_seconds / $time);
    if ($cur_time) {
        $back_text .= $cur_time.' '.$key;
        if ($cur_time != 1) $back_text .= 's';
        $back_text .= ', ';
        $back_in_seconds %= $time;
    }
}
$back_text = rtrim($back_text, ', ');
?>

<!--- etc. ---->
We are currently undergoing scheduled downtime to upgrade the site.
We'll be back in <!--?= $back_text ?-->. Thanks for your patience.

<!-- etc. -->

Should all be pretty straight-forward, but it’s good to let people know that you know you’re down and when you expect to be back.

Read more...