It’s been a long time in coming, but I’ve finally migrated from Drupal 7 to the static HTML-generating powerhouse that is Octopress. This is admittedly a bit of a departure from how I’ve previously done things — I’ve always been quite the server-side nerd, starting when I learned PHP in tenth grade — but so far I’m rather enjoying it. This marks the fifth iteration of the site, and I hope that I can finally focus more on posting content than on keeping Drupal modules updated.

Why the move?

Drupal is a nightmare to keep updated. While this is common amongst server-side open source content management systems, Drupal’s focus on small and modular bits of functionality means keeping a site up-to-date with the latest contrib code is somewhat of a challenge. And for a site I update maybe three or four times a year, the likelihood of having out-of-date code online is rather high.

That’d be the main reason, beyond the fact I never really needed the raw power that Drupal was able to provide. Yes, I tried making a “resume” content type at one point, but it wasn’t any easier to keep maintained than my LinkedIn account or the public Google Doc I sent as a PDF when I was still looking for work.

Additionally, I’ve started really enjoying the workflow provided by Git, particularly when combiend with the finesse and joie de vivre of GitHub. Not only can I host all my posts for free on GitHub pages, but I also have a great way of going back through my work and visualising the changes I’ve made over time. The hope is I can start publishing to my blog more regularly, as well as using it as a place to workshop writing.

Admittedly the theme needs a bit more work, but it’s nice to be back in Bootstrap Country.

I still think aendrew.com itself will always be more of portfolio site while my Tumblog will be more bloggy in content, but with any luck I can find the right balance to make both worthwhile.

-Æ.

The reason I created OpenMusicFestival in the first place was so that I could migrate MotionNotion.com from WordPress (And its unsupported WordTour plugin) to Drupal 7.

While I suspect my original use of WordTour for a music festival was slightly weird (The system was designed for small record labels, but relationship between artists, events and venues made it work for my purposes), I’m releasing my Artist migration class in case somebody finds it useful and wants to migrate to OpenMusicFestival. Note that this only migrates Artists — the Event and Venues parts are incomplete (On that note, if somebody wants to do those, I’ll happily both give you credit on the project as well as include the code with OMF.).

migrate_wordtour.php
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?php
/**
 * @file
 *  Migrate content from WordTour (WP) to OMF (Drupal)
 *
 *  NOTE: THIS IS WOEFULLY INCOMPLETE. I would *love* to give somebody credit
 *  for tidying up and completing this.
 */

class WordTourMigration extends Migration {
  public $wp_prefix = 'wp_'; //This is the entire first part of the table name.
  public $wp_db = '';
  public $wp_user = '';
  public $wp_pass = '';
  public $wp_host = 'localhost';

  public function __construct() {
    // Always call the parent constructor first for basic setup
    parent::__construct();

    // With migrate_ui enabled, migration pages will indicate people involved in
    // the particular migration, with their role and contact info. We default the
    // list in the shared class; it can be overridden for specific migrations.
    $this->team = array(
      new MigrateTeamMember('Ændrew Rininsland', 'aendrew@aendrew.com', t('Developer')),
    );

    // Individual mappings in a migration can be linked to a ticket or issue
    // in an external tracking system. Define the URL pattern here in the shared
    // class with ':id:' representing the position of the issue number, then add
    // ->issueNumber(1234) to a mapping.
    $this->issuePattern = 'http://drupal.org/node/:id:';
  }
}

/**
 * There are four essential components to set up in your constructor:
 *  $this->source - An instance of a class derived from MigrateSource, this
 *    will feed data to the migration.
 *  $this->destination - An instance of a class derived from MigrateDestination,
 *    this will receive data that originated from the source and has been mapped
 *    by the Migration class, and create Drupal objects.
 *  $this->map - An instance of a class derived from MigrateMap, this will keep
 *    track of which source items have been imported and what destination objects
 *    they map to.
 *  Mappings - Use $this->addFieldMapping to tell the Migration class what source
 *    fields correspond to what destination fields, and additional information
 *    associated with the mappings.
 */
class ArtistMigration extends WordTourMigration {
  public function __construct() {
    parent::__construct();

    //Set up other database
    Database::addConnectionInfo('wp', 'default', array(
          'driver' => 'mysql',
          'database' => $this->wp_db,
          'username' => $this->wp_user,
          'password' => $this->wp_pass,
          'host' => $this->wp_host,
          'prefix' => $this->wp_prefix,
        ));

    $this->description = t('Migrate the artists!');

    $this->map = new MigrateSQLMap($this->machineName,
        array(
          'artist_id' => array(
                           'type' => 'int',
                           'length' => 7,
                           'not null' => TRUE,
                           'description' => 'Artist ID',
                          )
        ),
        MigrateDestinationNode::getKeySchema()
      );

    $query = Database::getConnection('default', 'wp')
           ->select('wtr_artists', 'a');
    $query->join('wtr_attachment', 'at', 'a.artist_id = at.attachment_target_id');
    $query->join('wtr_attachment', 'att', 'a.artist_id = att.attachment_target_id');
    $query->join('postmeta', 'pm', 'pm.post_id = att.attachment_type_id');
    $query->fields('a',
              array(
                  'artist_id',
                  'artist_name',
                  'artist_publish_date',
                  'artist_bio',
                  'artist_record_company',
                  'artist_social_links',
              )
            );
    //$query->fields('att', array('attachment_info'));
    $query->fields('pm', array('meta_value'));
    //$query->addField('pm', 'meta_value', 'photo');
    $query->addExpression('GROUP_CONCAT(DISTINCT at.attachment_info)', 'genres'); //Pull in genres.
    $query->condition('at.attachment_target', 'artist');
    $query->condition('at.attachment_type', 'genre');
    $query->condition('att.attachment_type', 'thumbnail');
    $query->condition('pm.meta_key', '_wp_attached_file');
    $query->groupBy('a.artist_id');


    // Create a MigrateSource object, which manages retrieving the input data.
    $this->source = new MigrateSourceSQL($query, array(), NULL, array('map_joinable' => FALSE));

    // Set up our destination
    $this->destination = new MigrateDestinationNode('artist', array('text_format' => 'full_html'));

    // Assign mappings TO destination fields FROM source fields.
    $this->addFieldMapping('title', 'artist_name');
    $this->addFieldMapping('uid')
         ->defaultValue(1);
    $this->addFieldMapping('changed', 'artist_publish_date');
    $this->addFieldMapping('status')
         ->defaultValue(1);
    $this->addFieldMapping('promote', '')
         ->defaultValue(0);
    $this->addFieldMapping('sticky', '')
         ->defaultValue(0);
    $this->addFieldMapping('revision')
         ->defaultValue(0);
    $this->addFieldMapping('log')
         ->defaultValue('Migrated to Drupal 7 from WordPress.');
    $this->addFieldMapping('comment')
         ->defaultValue(1);
    $this->addFieldMapping('body', 'artist_bio')
          ->arguments(array('format' => 'full_html'))
          ->description('See prepareRow()');
    $this->addFieldMapping('created', 'artist_publish_date');
    $this->addFieldMapping('field_labels', 'artist_record_company')
          ->separator(', ')
          ->arguments(array('create_term' => true));
    //$this->addFieldMapping('path', '');
    //$this->addFieldMapping('pathauto', '');
    $this->addFieldMapping('field_photo', 'meta_value');
    $this->addFieldMapping('field_photo:source_dir')
         ->defaultValue('/Users/aendrew/Sites/mn_wp');
    $this->addFieldMapping('field_photo:preserve_files')
         ->defaultValue(true);
    $this->addFieldMapping('field_photo:destination_file', 'meta_value');
    $this->addFieldMapping('field_photo:file_replace')
         ->defaultValue(MigrateFile::FILE_EXISTS_REUSE);
    $this->addFieldMapping('field_photo:alt', 'artist_name');
    $this->addFieldMapping('field_photo:title', 'artist_name');
    $this->addFieldMapping('field_links', 'artist_social_links')
         ->description('See prepare()'); //Needs to be unserialized
    $this->addFieldMapping('field_genres', 'genres')
          ->separator(',')
          ->arguments(array('create_term' => true));
  }

  public function prepareRow($row) {
    //Prepare Thumbnails
    $thumb = trim($row->meta_value);
    //watchdog('migrate', 'Filename is ' . $thumb);
    $row->meta_value = 'wp-content/uploads/' . $thumb;
  }

  public function prepare(stdClass $node, stdClass $row) {
    //Set up the links
    $links = unserialize($row->artist_social_links);
    $empty = 'a:10:{s:13:"artist_flickr";s:0:"";s:14:"artist_youtube";s:0:"";s:12:"artist_vimeo";s:0:"";s:15:"artist_facebook";s:0:"";s:14:"artist_twitter";s:0:"";s:13:"artist_lastfm";s:0:"";s:14:"artist_myspace";s:0:"";s:15:"artist_bandcamp";s:0:"";s:13:"artist_tumblr";s:0:"";s:19:"artist_reverbnation";s:0:"";}';
    if ($node->field_links[LANGUAGE_NONE][0]['url'] == $empty) {
      unset($node->field_links);
    } else {
      $i = 0;
      foreach ($links as $site => $link) {
        if (!empty($link)) {
          $site_name = ucfirst(str_replace('artist_', '', $site));
          if ($site_name == "Youtube") $site_name = "YouTube";
          if ($site_name == "Lastfm") $site_name = "Last.fm";
          if ($site_name == "Youtube") $site_name = "YouTube";
          if ($site_name == "Myspace") $site_name = "MySpace";
          $node->field_links[LANGUAGE_NONE][$i]['title'] = $site_name;
          $node->field_links[LANGUAGE_NONE][$i]['url'] = urldecode($link);
          $i++;
        }
      }
    }
  }
}

Did this help you out? Have I saved you a tonne of time? Please leave me a comment letting me know!

After Minehost went belly-up, the communal Minecraft server I played on went dead —and stayed dead — for about 6 months. However, I have revived it, on brand spanking new hardway and several major iterations of Minecraft later! Not only that, but it’s running the bad-assery that is Tekkit Lite, which means you can do crazy things like build automated mining robots, write simple computer programs in-game, harness the power of volcanoes, even create whole new dimensions via books like in Myst.

Seriously, I have no idea why it took me so long to get on top of Tekkit. That shiz’s cray.

Because there’s not much in terms of anti-griefing code on the server right now, it’s invitation-only. Please contact me either via email, Twitter, Facebook, etc. if you want the address.

Over the last few months working with Drupal, I’ve wanted to become a more productive member of the community. To that end, I’ve been working on getting a full project approved in order to get “Git vetted” and thus be able to create full projects on Drupal.org.

A bunch of work, six code reviews and a boatload of PAReview’ing later, I now have full project access and have started by creating GitHub Pages, which allows users to post copies of individual nodes to GitHub’s free HTML page hosting service. This is a core bit of functionality for VizCloud, intended to reduce server load on high-traffic nodes (For instance, a data visualization embedded in a news article).

Let me know if you like it or have any comments! I need to refine it a bunch still too — and absolutely love it when people submit patches in the issue queue.

drupal.org/project/ghpages

OpenMusicFestival is a Drupal distribution enabling music festivals of all sizes to create a rich, semantically-enabled website. Functionality includes extensive artist listings (Complete with Soundcloud and YouTube streams), schedules, venue listings — and because it’s Drupal, more functionality like ticket sales and forums are only a module installation away.

OpenMusicFestival.com Twitter Drupal.org Project

Want to help out? I need a logo and a theme — would love to collaborate with anyone willing to help out with those!

In my ongoing quest to make a non-Java-based ManyEyes clone, I have launched VizCloud, a Drupal distribution intended to allow simple dataset upload and visualization construction. I pretty much have the dataset parts down (Provided and maintained via SocialCalc through Sheetnode, though I’m currently still developing the visualization aspect (Provided via d3.js — anyone with experience creating models in d3, please get back to me!).

VizCloud.org

Twitter

GitHub

Use Koding.com? Want to install Drupal — with Drush — really easily? Now you can, with my snazzy new Drupal Installer app. Just add it to your account’s apps via GitHub and you’ll have both a way to deploy Drupal in under 5 minutes, but also a control panel to manage your various sites.

Free cloud-based Drupal website hosting? Yes, please!

GitHub

For a project I did for the Economist, I created a treemap script that could be easily updated via Google Spreadsheets every day, allowing a running tally of Olympics medals for each country and event.

The treemap was created via The Javascript Infoviz Toolkit, with Google Spreadsheet data pushed via PHP.