Bringing the NFL Draft to your phone

At the end of April, I had the privilege of working on a small, but wide-reaching experimental mobile web app project for the NFL Draft (April 28-30 2011).

The mobile web app was launched on April 28 and promoted across all Gannett properties, including USA TODAY. The app utilized the JQuery mobile framework as a first-run experiment to test it along with our news APIs and content management systems at Gannett.

The NFL Draft app featured:

- Real-time draft picks on draft day (internal API provided by USA TODAY)
- Latest news on the NFL Draft from USA TODAY’s API and NFL news feeds from relevant Gannett properties
- Team pages for each NFL team featuring related news and picks
- College pages for the relevant college teams, with players in the draft and related news
- Player profile pages for each of the draftees

I personally worked on all features of the NFL Draft app with the exception of draft-day picks. This web app was built in PHP and used the JQuery mobile framework.

The site is no longer active but I’m posting some screenshots here! Continue reading

Migrating from Drupal to WordPress

I recently migrated Georgetown’s Communication, Culture & Technology Journal Gnovis from Drupal to WordPress and I thought I would share how I did it.

In the coming weeks I will be working with Georgetown’s Gnovis team to redesign the site entirely. The first step in this process was migrating the site from Drupal to WordPress at the request of the editors who prefer the WordPress CMS over Drupal.

Before starting the site redesign, I wanted to make sure I backed up the Drupal database, which consists of journal articles, blog posts and event posts.

**MAKE SURE YOU BACK UP YOUR DRUPAL DATABASE SO YOU WON’T LOSE ANY DATA**

Once you have a backup, you’ll want to install WordPress on your webserver, create a database for this WordPress site, then copy the Drupal database into you new WordPress database and cleanup Drupal/Wordpress differences using the scripts below to complete the migration.

1. Install WordPress

– Download WordPress and unzip this file on your webserver.
– Create an empty mySQL database for WordPress using phpMyAdmin or your host.
– Configure your WordPress installation going to your site ../wp-config.php (EX:www.mysite.com/wp-config.php)
– Remove any posts and pages that already exist in your fresh WordPress site, including the “About” demo page and “Hello World” post.

2. Copy the Drupal database into your new WordPress database

– Login into phpMyAdmin.
– Click on the old Drupal database in phpMyAdmin and select the tab “Operations“
– In the section “Copy database to:“, fill in the name of the WordPress database
– Leave the selection on “Structure and data”
– Deselect “CREATE DATABASE before copying“, because we have already the database
– Select “Add DROP TABLE / DROP VIEW“
– Select “Add AUTO_INCREMENT value“
– Leave the other selections off
– Now press on the Go-button, to copy the database

* For my Gnovis migration, I created a new MySQL database called “gnovis_wp” (you will want to find and replace this with the name of your new WordPress database (Example: “wordpress”). If you add a prefix to your WordPress database such as “wp_” you will also need to refine the following to take that into account.

3. Converting the Drupal tables into WordPress database tables

Here are a few scripts I adapted from other scripts to complete the migration:
** I highly recommend editing and pasting each item separately into your SQL editor so that they complete **

To run the conversion script, you need to perform the following steps:

– Login into phpMyAdmin
– Click on the new WordPress database
– Click on the tab “SQL“
– Select the above script and copy it (Ctrl-C)
– Open your favorite editor and paste (Ctrl-V) the script in the editor
– Comment out VIDEO and IMAGE if applicable
– Select the complete script in the editor (Ctrl-A) and past it in the “SQL” tab
– Press the Go-button to start the conversion
– It is possible to re-run the script more than once in case of problems.

# TAGS
# Using REPLACE prevents script from breaking if Drupal contains duplicate terms.
#REPLACE INTO gnovis_wp.wp_terms
   # (term_id, `name`, slug, term_group)
   # SELECT DISTINCT
#	d.tid, d.name, REPLACE(LOWER(d.name), ' ', '_'), 0
   # FROM gnovis_wp.term_data d
   # INNER JOIN gnovis_wp.term_hierarchy h
#	USING(tid)
   # INNER JOIN gnovis_wp.term_node n
#	USING(tid)
   # WHERE (1
	 # This helps eliminate spam tags from import; uncomment if necessary.
	 #AND LENGTH(d.name) < 50
   # )
#;

#INSERT INTO gnovis_wp.wp_term_taxonomy
   # (term_id, taxonomy, description, parent)
    #SELECT DISTINCT
	#d.tid `term_id`,
	#'post_tag' `taxonomy`,
	#d.description `description`,
	#h.parent `parent`
    #FROM gnovis_wp.term_data d
    #INNER JOIN gnovis_wp.term_hierarchy h
#	USING(tid)
    #INNER JOIN gnovis_wp.term_node n
	#USING(tid)
    #WHERE (1
	 # This helps eliminate spam tags from import; uncomment if necessary.
	 #AND LENGTH(d.name) < 50
    #)
#;

# POSTS
# Keeps private posts hidden.
INSERT INTO gnovis_wp.wp_posts
    (id, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt,
post_name, post_modified, post_modified_gmt, post_type, `post_status`)
    SELECT DISTINCT
	n.nid `id`,
	n.uid `post_author`,
	FROM_UNIXTIME(n.created) `post_date`,
	FROM_UNIXTIME(n.created-3600) `post_date_gmt`,
	r.body `post_content`,
	n.title `post_title`,
	r.teaser `post_excerpt`,
	IF(SUBSTR(a.dst, 11, 1) = '/', SUBSTR(a.dst, 12), a.dst) `post_name`,
	FROM_UNIXTIME(n.changed) `post_modified`,
	FROM_UNIXTIME(n.changed-3600) `post_modified_gmt`,
	n.type `post_type`,
	IF(n.status = 1, 'publish', 'private') `post_status`
    FROM gnovis_wp.node n
    INNER JOIN gnovis_wp.node_revisions r
	USING(vid)
    LEFT OUTER JOIN gnovis_wp.url_alias a
	ON a.src = CONCAT('node/', n.nid)
    # Add more Drupal content types below if applicable.
    WHERE n.type IN ('post', 'page', 'blog')
;

# Fix post type;
# Add more Drupal content types below if applicable.
UPDATE gnovis_wp.wp_posts
    SET post_type = 'post'
    WHERE post_type IN ('blog')
;

# Set all pages to "pending".
# If you're keeping the same page structure from Drupal, comment out this query
# and the new page INSERT at the end of this script.
UPDATE gnovis_wp.wp_posts SET post_status = 'pending' WHERE post_type = 'page';

# POST/TAG RELATIONSHIPS
INSERT INTO gnovis_wp.wp_term_relationships (object_id, term_taxonomy_id)
    SELECT DISTINCT nid, tid FROM gnovis_wp.term_node
;

# Update tag counts.
UPDATE term_taxonomy tt
    SET `count` = (
	SELECT COUNT(tr.object_id)
	FROM term_relationships tr
	WHERE tr.term_taxonomy_id = tt.term_taxonomy_id
    )
;

# COMMENTS
# Keeps unapproved comments hidden.

INSERT INTO gnovis_wp.wp_comments
    (comment_post_ID, comment_date, comment_date_gmt, comment_content,
comment_parent, comment_author, comment_author_email, comment_author_url, comment_approved)
    SELECT DISTINCT
	nid, FROM_UNIXTIME(timestamp), FROM_UNIXTIME(timestamp-3600), comment,
thread, name,
	mail, homepage, ((status + 1) % 2)
    FROM gnovis_wp.comments
;

# Fix taxonomy;
UPDATE IGNORE gnovis_wp.wp_term_relationships,
gnovis_wp.wp_term_taxonomy
    SET gnovis_wp.wp_term_relationships.term_taxonomy_id =
gnovis_wp.wp_term_taxonomy.term_taxonomy_id
    WHERE gnovis_wp.wp_term_relationships.term_taxonomy_id =
gnovis_wp.wp_term_taxonomy.term_id
;

# OPTIONAL ADDITIONS ‚ REMOVE ALL BELOW IF NOT APPLICABLE TO YOUR CONFIGURATION

# CATEGORIES
# These are NEW categories, not in gnovis_wp.wp_gnovis_ Add as many sets as needed.
INSERT IGNORE INTO gnovis_wp.wp_terms (name, slug)
    VALUES
    ('First Category', 'first-category'),
    ('Second Category', 'second-category'),
    ('Third Category', 'third-category')
;

# Set category names to title case (in case term already exists [as a tag] in lowercase).
UPDATE gnovis_wp.wp_terms SET name = 'First Category' WHERE name = 'first category';
UPDATE gnovis_wp.wp_terms SET name = 'Second Category' WHERE name = 'second category';
UPDATE gnovis_wp.wp_terms SET name = 'Third Category' WHERE name = 'third category';

# Add categories to taxonomy.
INSERT INTO gnovis_wp.wp_term_taxonomy (term_id, taxonomy)
    VALUES
    ((SELECT term_id FROM wp_terms WHERE slug = 'first-category'), 'category'),
    ((SELECT term_id FROM wp_terms WHERE slug = 'second-category'), 'category'),
    ((SELECT term_id FROM wp_terms WHERE slug = 'third-category'), 'category')
;

# Auto-assign posts to category.
# You'll need to work out your own logic to determine strings/terms to match.
# Repeat this block as needed for each category you're creating.
INSERT IGNORE INTO gnovis_wp.wp_term_relationships (object_id, term_taxonomy_id)
    SELECT DISTINCT p.ID AS object_id,
	(SELECT tt.term_taxonomy_id
	FROM gnovis_wp.wp_term_taxonomy tt
	INNER JOIN gnovis_wp.wp_terms t USING (term_id)
	WHERE t.slug = 'enter-category-slug-here'
	AND tt.taxonomy = 'category') AS term_taxonomy_id
    FROM gnovis_wp.wp_posts p
    WHERE p.post_content LIKE '%enter string to match here%'
    OR p.ID IN (
	SELECT tr.object_id
	FROM gnovis_wp.wp_term_taxonomy tt
	INNER JOIN gnovis_wp.wp_terms t USING (term_id)
	INNER JOIN gnovis_wp.wp_term_relationships tr USING (term_taxonomy_id)
	WHERE t.slug IN ('enter','terms','to','match','here')
	AND tt.taxonomy = 'post_tag'
    )
;

# Update category counts.
UPDATE wp_term_taxonomy tt
    SET `count` = (
	SELECT COUNT(tr.object_id)
	FROM wp_term_relationships tr
	WHERE tr.term_taxonomy_id = tt.term_taxonomy_id
    )
;

# AUTHORS
INSERT IGNORE INTO gnovis_wp.wp_users
    (ID, user_login, user_pass, user_nicename, user_email,
    user_registered, user_activation_key, user_status, display_name)
    SELECT DISTINCT
	u.uid, u.mail, NULL, u.name, u.mail,
	FROM_UNIXTIME(created), '', 0, u.name
    FROM gnovis_wp.users u
    INNER JOIN gnovis_wp.users_roles r
	USING (uid)
    WHERE (1
	# Uncomment and enter any email addresses you want to exclude below.
	# AND u.mail NOT IN ('test@example.com')
    )
;

# Assign author permissions.
# Sets all authors to "author" by default; next section can
# selectively promote individual authors
INSERT IGNORE INTO gnovis_wp.wp_usermeta (user_id, meta_key, meta_value)
    SELECT DISTINCT
	u.uid, 'capabilities', 'a:1:{s:6:"author";s:1:"1";}'
    FROM gnovis_wp.users u
    INNER JOIN gnovis_wp.users_roles r
	USING (uid)
    WHERE (1
	# Uncomment and enter any email addresses you want to exclude below.
	# AND u.mail NOT IN ('test@example.com')
    )
;
INSERT IGNORE INTO gnovis_wp.wp_usermeta (user_id, meta_key, meta_value)
    SELECT DISTINCT
	u.uid, 'user_level', '2'
    FROM gnovis_wp.users u
    INNER JOIN gnovis_wp.users_roles r
	USING (uid)
    WHERE (1
	# Uncomment and enter any email addresses you want to exclude below.
	# AND u.mail NOT IN ('test@example.com')
    )
;

# Change permissions for admins.
# Add any specific user IDs to IN list to make them administrators.
# User ID values are carried over from gnovis_wp.wp_gnovis_
UPDATE gnovis_wp.wp_usermeta
    SET meta_value = 'a:1:{s:13:"administrator";s:1:"1";}'
    WHERE user_id IN (1) AND meta_key = 'capabilities'
;
UPDATE gnovis_wp.wp_usermeta
    SET meta_value = '10'
    WHERE user_id IN (1) AND meta_key = 'user_level'
;

# Reassign post authorship.
UPDATE gnovis_wp.wp_posts
    SET post_author = NULL
    WHERE post_author NOT IN (SELECT DISTINCT ID FROM gnovis_wp.wp_users)
;

# Fix post_name to remove paths.
# If applicable; Drupal allows paths (i.e. slashes) in the dst field,
# but this breaks
# WordPress URLs. If you have mod_rewrite turned on,
# stripping out the portion before
# the final slash will allow old site links to work properly,
# even if the path before
# the slash is different!
UPDATE gnovis_wp.wp_posts
    SET post_name =
    REVERSE(SUBSTRING(REVERSE(post_name),1,LOCATE('/',REVERSE(post_name))-1))
;

# Miscellaneous clean-up.
# There may be some extraneous blank spaces in your Drupal posts;
#use these queries or other similar ones to strip out the undesirable tags.
UPDATE gnovis_wp.wp_posts
    SET post_content = REPLACE(post_content,'

','')
;
UPDATE gnovis_wp.wp_posts
    SET post_content = REPLACE(post_content,'

','')
;

# NEW PAGES, READ BELOW AND COMMENT OUT IF NOT APPLICABLE
TO YOUR SITE
# MUST COME LAST IN THE SCRIPT AFTER ALL OTHER QUERIES!
# If your site will contain new pages, you can set up the basic structure
#for them here.
# Once the import is complete, go into the WordPress admin and copy content
#from the Drupal
# pages (which are set to "pending" in a query above) into the appropriate
#new pages.
INSERT INTO gnovis_wp.wp_posts
    (`post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`,
    `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`,
    `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`,
    `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`,
    `post_mime_type`, `comment_count`)
    VALUES
    (1, NOW(), NOW(), 'Page content goes here, or leave this value empty.', 'Page Title',
    '', 'publish', 'closed', 'closed', '',
    'slug-goes-here', '', '', NOW(), NOW(),
    '', 0, 'http://full.url.to.page.goes.here', 1, 'page', '', 0)
;

Once you have completed your SQL scripts you should have all of your Drupal content in your WordPress database. Check your WordPress installation and you should see your posts, authors, comments, etc. have moved over to your WordPress site, as shown here:

Are you a Connector?

An inside look at my new Facebook app and how I made it

Inspired by my recent dive into social network analysis and Malcolm Gladwell’s book “The Tipping Point,” I decided to make a little Facebook app: Are you a Connector?

When I read “The Tipping Point” a couple of years ago, I, like I assume others have, used Gladwell’s test to see if I am a ‘Connector’ by counting up the number of my contacts with the surnames from his list.

I got a score in the low 20′s then and I’m still around 23 today — this time according to my Facebook connections. So I thought it would be fun to make a Facebook app that uses the Graph API to match the surnames of your Facebook friends with those on Gladwell’s list to give you a score the same way his test does. It’s not an exact science, but generally if you have significantly more than the average score for your age range then you are likely a Connector. Kind of cool, right?

So if you check out my app you’ll see I explain what a connector is (someone who knows a lot of people, more than most) and what your score means. You won’t get a straight answer such as “Congrats! You’re a Connector!” or “Sorry, dude. You’re not a Connector. Have you tried Meetup.com?” because there is no exact value to confirm whether or not you are. You can check out the chart I made (right) based on Gladwell’s averages to see where you fall for your age range and see that you are either about average, slightly above average or way above average in Gladwell’s ‘Connector’ zone.

For more background on the ‘Connector’ test, read “The Tipping Point” or check out Gladwell’s excerpt on his blog.

How I created the app

I was pleasantly surprised by how easy it was to get started creating a Facebook app. (Go to http://facebook.com/developers to get started with your own app!) I decided to use Facebook’s PHP SDK and couldn’t believe that it took me only 4 hours (from about 10 p.m. to 2 a.m. MST Sunday night) to create my app. Then I did a little IE debugging and cleaned it up before sharing it with my fellow Facebook friends tonight — super easy!

So, following Gladwell’s guidelines for getting a ‘Connector’ score, I set up my new Facebook app by downloading the PHP SDK from GitHub and using that to pull in the Graph API.

The SDK makes it easy for you to pull in user Facebook data using the Graph API:

if ($session) {
  try {
    $uid = $facebook->getUser(); //This (obviously) gets the user
    $me = $facebook->api('/me'); //This pulls the user's Facebook data

And I just added one more variable to grab the app user’s list of friends:

$myFriends = $facebook->api('/me/friends');

Then I created an array of the friends’ names:

$friendsData = $myFriends['data'];

To finish it up, I looped through each friend’s name, adding a test for people with three+ names and a test to see if each last name matched one of the 250 surnames from Gladwell’s list. And voilà — your count = your ‘Connector’ score.

Your Score: $count;

Some snags I ran into:

  • For whatever reason (something having to do with Canvas), there is a bug in IE (of course) that causes iframed Facebook apps to keep refreshing the page over and over. To fix this, just add this before the HTML:
    header('P3P: CP="CAO PSA OUR"');
    
  • You need to request permission to post results, status updates, or get other user info like birthdays and work history if you want to use it in your app. To do this add the following to your authentication code and you should be set (See Facebook’s developer docs on extended permissions):
        $session = $facebook->getSession();
        $loginUrl = $facebook->getLoginUrl(
                array(
                'canvas'    => 1,
                'fbconnect' => 0,
                'req_perms' => 'email,publish_stream,status_update,user_birthday,'
                )
        );
    

So that’s it. I hope you enjoy the app! And as always, I welcome feedback and questions.

The Long Road

It’s the middle of July and I just ended a 4  1/2 -week graduate course in computer science at Georgetown.  Why am I writing this in a blog post?  Because I wanted to share this experience.

Before I go into any detail, something you should know:

Would I recommend taking a condensed course in computer science over the summer? No.

Would I recommend taking a computer science course in general? Yes.

Like a lot of people, even when I was in high school and undergrad I felt I missed the boat to learn computer programming.  But I’m happy to say that now, as a graduate student, I’ve finally learned (accepted) that it’s never too late to learn anything — you never know how difficult something is until you try it.

That said, computer science isn’t something you can learn by osmosis. It requires practice. Lots of practice.  So, if you’re thinking about learning, don’t take a condensed course like I did— especially if you have a full-time job and are taking it at the graduate level (With all math and comp sci grads…)  You’ll thank yourself later when you realize you’ve taken a longer path that’s allowed you to absorb the material and learn more.

Despite the brief course, I’m grateful for the short and intense learning experience I had. I was forced to immerse myself in all things C++ and OOP — and true immersion isn’t something you can fake.  Like any foreign language, programming languages require you to spend a lot of time actually using them. There’s no easy way to pick it up other than using it. Learning a language is hard and it helps when you’re able to talk about it and learn from experts in a class environment.  Courses also help you budget your time and projects in a proven sequence. If it isn’t obvious, I’m a fan of courses because you gain the benefit of sharing and learning from experts and others in the class.

If you can’t afford to take a course, I know a lot of programmers who are self-taught who are great at what they do.  Whether you decide to take a course or not, my only advice is that you dedicate a lot of time to learning programming. It’s time-intensive, but it will pay off in the end. You can’t BS your way around it.

Why I’m writing this

This class I took was kind of the beginning of a major overhaul of the way I’ve gone about teaching myself web development and design techniques in the past.  Like a lot of people, I’ve learned a lot by dabbling in tutorials and teaching myself as I go. Even before this class, I wasn’t exactly a novice in web development, but this summer I’m trying to ‘step my game up’ — and in doing so, I’m going to share my projects and works in progress on this blog.

Why programming?

I’ve found learning web development and programming to be extremely rewarding in my work as a multimedia designer for news applications.  Having the ability to build and execute multimedia products from start to finish has been an extremely satisfying aspect of my job.  It used to be that web projects I wanted to do ended up taking forever for someone else to develop or wouldn’t get done at all because of the lack of expertise in my department.  Now, I’m able to create whole web pages on the fly. Knowledge is power.   And programming is problem solving. It teaches you not only how to literally build something, but also how to logically solve problems in ways most people aren’t used to.

I’m hoping to use this blog to keep track of my current and upcoming web design and development projects so you can see what I’m up to. I’ll also post a note about some things I’ve done in the last year or so, but this blog is really going to be about going forward. Please feel free to let me know know what you think and ask me questions!

Sketching with Processing

Those who know me know I love my chalkboard wall. I wanted to try to recreate a virtual chalkboard using Processing – which is very simple. Check it out by clicking to draw on the “chalkboard” above.

If you haven’t checked out Processing, you should. Processing is an open source programming language and environment for people who want to create images, animations, and interactions.

Here’s how simple it is to create a simple chalkboard like the one above using Processing:

void setup() {
  size(590, 400);
  fill(255);
  background(51);
}

void draw() {
  if(mousePressed) {
    stroke(255);
  } else {
    noStroke();
  }
  line(mouseX-1, mouseY, mouseX+1, mouseY);
  line(mouseX, mouseY-1, mouseX, mouseY+1);

}

Here are a few uses of Processing that have inspired me:

Just Landed: Processing, Twitter, MetaCarta & Hidden Data by Jer Thorp

On the Origin of Species: The Preservation of Favoured Traces by Ben Fry

Solar with lyrics by Robert Hodgin