Uncategorized


6
Jun 11

Hello San Francisco!

If you’ve been wondering why I haven’t been writing much on the blog recently, here is why: I just got my visa to the US and moved to San Francisco for an internship! I’ll be here for the next 12 months on that visa. That process + wrapping up my life in Finland took a lot of my free time, the rest went to reviewing a coming-soon Kohana 3 book from Apress for which I’m a technical reviewer, and my writing project related to Node.js which I am hoping to move forward soon. I also gave a talk at Frontend.fi on Node (my first tech talk), and released a small library (node-winamp) to control Winamp over LAN from the console using Node.

My new years resolution was to ship more, but I never expected to ship my own ass over to San Francisco! It’ll be awesome.

I flew in on June 3rd, and have been here for two days. So far, everything has been pretty awesome, though I still need to find an apartment rental and get the final paperwork done on this side!

Since I’ll be able to concentrate more on code instead of living the triple life of a doctoral student, individual entrepreneur and open source programmer, I hope to start building a solid streak of open source/fun coding while I’m here. I read something on HN recently which I think will be good to keep in mind:

“I have a rule: I refuse to go to sleep if I haven’t read and written some code on a given day (this doesn’t include the code I write for work, of course). So far, this rule has had a positive impact on my ability to code.”

I’ll try to apply that rule, perhaps using 42goals.com or the Calendar About Nothing

 


15
Nov 10

How to install gnome-color-chooser for Fedora

Gnome-color-chooser is a tool which allows you to control the colors used in GNOME with a lot more granularity than the default “Appearance” app.

Unfortunately, Fedora does not have a repository with a gnome-color-chooser rpm, so you will need to compile it yourself.

Here are the instructions, tested with gnome-color-chooser v. 0.2.5 and Fedora 13/14:

su
yum install wget gcc-c++ glib-devel intltool gtkmm24-devel libglademm24-devel libgnomeui-devel libgnome-devel libxml2-devel

wget http://downloads.sourceforge.net/project/gnomecc/gnome-color-chooser/0.2.5/gnome-color-chooser-0.2.5.tar.gz

tar -xzvf gnome-color-chooser-0.2.5.tar.gz

mv gnome-color-chooser-0.2.5 /opt/gnome-color-chooser

cd /opt/gnome-color-chooser/

./configure

make

make install

After installing, you can find it under System -> Preferences -> Gnome Color Chooser.


5
Nov 10

4 simple ways startup people deceive themselves

One of my many favorite quotations is one from Demosthenes: “Nothing is easier than self-deceit. For what each man wishes, that he also believes to be true”. Nietzsche puts it in another way: “The visionary lies to himself, the liar only to others”.

Startup people really need to be a bit nuts in order to get anything to done. I was reminded of this today while reading this Hacker News thread. Please note that I am not writing this as criticism to this particular founder, I am certain the same criticisms apply to myself and many others.

There are a number of beliefs that are useful and false that startup people need to/tend to hold:

1. The prerequisites-will-save-me belief

You remember how going to college you were told that getting your degree will be useful in getting a job? Especially now that the economy isn’t great, I’ve seen tons of people complaining about how getting a degree did not guarantee that they got a job. Of course, that was not the actual claim – it was only implied that filling in the prerequisites would lead to success.

Starting something new, you have to believe that filling in the prerequisites for success (whatever those are: an extensive plan, a completed prototype, an encouraging response) will be sufficient for actual success. There are so many things that are out of our hands that all we can do is to do our own part and hope/believe that that will be enough, since there are many things that you cannot influence effectively. I have done the prerequisites, so I must get – nay, I deserve – funding, traction and all the other good things.

It isn’t false that doing the right prerequisites is necessary. It’s just that doing the prerequisites is not a guarantee for anything; and that knowing what is the right thing to do is hard.

2. The idea-is-important belief

I see this a lot among founders, particularly non-technical founders. It states that all you need is great idea, and everything else will fall into place. How long can it take to create and launch a product now that I have a great idea? The origin of this belief is that we tend to underestimate the amount of work and persistence needed, particularly for those things that we have little or no experience doing.

For non-technical founders, it’s the part where you take a rough idea and make it into something that actually works and is nice to use – which implies a million small details that have to be done correctly and which always takes longer than expected. For technical founders, it’s the part where you get someone to care about what you are doing: whether this is getting funding, or getting traction with partners, or getting someone to buy your product. We tend to like to think that whatever we do not understand must not be particularly hard or time-consuming.

It isn’t false that a good idea is useful; indeed, there are tons of examples of a simple, insightful idea leading to huge success. It’s just that those ideas are outliers: if they weren’t, we wouldn’t consider them so remarkable. VC’s say the invest in the team, not the idea – which probably is both true to some extent and false to some extent.

If you hold this belief strongly, then you tend to be secretive of your idea. Here is a thought experiment I came up with a while ago: Give someone the lottery numbers for next week. See if they act on it. They probably won’t. In fact, try to convince them that you have some strange scheme that allows you to know the lottery numbers. I’m not saying startups are a lottery, it’s just that you are claiming to have some extremely valuable idea based on some chain of logic. You’ll still have a hard time convincing people, let alone getting them to part with their money.

If you don’t love your idea, you won’t have the persistence to make it work. But at the same time, in retrospect, most ideas are garbage.

3. The can’t-do-it-without-money belief

This is frequently associated with potentially game changing startups (=founders believe it is potentially game changing). The assertion goes something like this: I have all the other parts, now I just need someone to add money in order to make the next big thing. This is also the reason why VC funding exists, and does make profit for some people.

This may or may not be correct; there are many companies that need funding in order to reach a sustainable size, or to capture an early market. And you might be one of those companies, which means that you need to take an extremely proactive approach to getting funding.

The problem with this belief is, however, that there are still many variations of ideas that can be done with less capital, and I do believe that you should believe enough in your company to do whatever you can whether or not someone pays you a “salary” to do something. Yes, you can do a lot more if you can focus and not worry about money, but VC’s aren’t a charity – which implies you should do what you can to achieve traction before deciding not to put in more time without funding. At the same time, you need to recognize when you have done all you can without money, and then decide whether to move on.

Of course, how much you should do before calling it quits is hard to answer – perhaps you have done everything you can to polish the concept and raise funding, and should call it quits, or perhaps funding is waiting just around the corner?

4. The persistence-is-omnipotent or I-will-make-it belief

This is the belief that iterating on a product will eventually lead to something successful. Since traction is rarely instant, it is rather useful to believe that whatever issues have come up so far can be solved in the next iteration of the product. Famously, PayPal started as a service reconciling beamed payments from Palm Pilots with email payments as a feature till they realized what they should be doing.

But despite the fact that you are smart and special, most startups statistically fail. And believing that you can make a turd work will probably just waste your time. Satisfaction with the current situation and expected progress is a prerequisite for wasting time.

“No matter how far down the wrong road you’ve traveled, always turn back.” But of course knowing whether you are on the wrong road is hard, and it just might be that keeping the idea alive for another week will result in traction. Or not. You won’t know, unless you believe that you should try.

Conclusions

What is interesting about these beliefs is that they are both useful and harmful: both true and false depending on what the exact situation is.

Confidence doesn’t directly influence your success rate, but it raises your attempt rate. And as Wayne Gretzky said, you miss 100% of the shots you never take.

Most people are overly preoccupied with the costs and risks of doing something. Entrepreneurs have to be optimists by nature, but try not engage in (too much) self-deceit. Edward Benson put it this way: “How desperately difficult it is to be honest with oneself. It is much easier to be honest with other people.”


6
Oct 10

Tip: Disable CakePHP model auto-load and trace the load paths

Here is how to disable the model auto-loading in CakePHP:

// .. around line 140 in /cake/libs/class_registry.php
} elseif ($type === 'Model') {
   if ($plugin && class_exists($plugin . 'AppModel')) {
      $appModel = $plugin . 'AppModel';
   } else {
      $appModel = 'AppModel';
   }
   $settings['name'] = $class;
   $ai =& App::getInstance();
   $search = $ai->__paths('Model');
   $pathstr = '';
   $file = Inflector::underscore($class.'.php');
   foreach ($search as $path) {
      $path = rtrim($path, DS);
      if ($path === rtrim(APP, DS)) {
         $recursive = false;
      }
      if (true === false) {
         $pathstr .= $path . DS . $file.'|';
         continue;
      }
      if (!isset($this->__paths[$path])) {
         if (!class_exists('Folder')) {
            require LIBS . 'folder.php';
         }
         $Folder =& new Folder();
         $directories = $Folder->tree($path, false, 'dir');
         $this->__paths[$path] = $directories;
      }
      foreach ($this->__paths[$path] as $directory) {
         $pathstr .= $path . DS . $file.'|';
      }
   }
   die("unable to find class $type, $plugin$class "
   ."(search path: ".$pathstr.")");
   ${$class} =& new $appModel($settings);
}

25
Jul 10

Kohana3: Setting up phpUnit and phpDocumentor

Here is my quick guide on how to setup phpUnit and phpDoc for Kohana 3.

phpUnit

1. Setup

Install phpUnit using pear:

pear install phpunit

(See the PEAR site for how to install PEAR.)
Get the Kohana unittest module from http://github.com/kohana/unittest. If you aren’t using git for your repo, just clone the files (git clone http://github.com/kohana/unittest.git), get rid of the .git subdirectories and copy the module to your modules/ directory.

Follow the setup instructions on GitHub. Some tips:

It may be easiest to define an alternative default database configuration, since currently unittest does not automatically change any of the database configurations (ex. in models). Since I use a Fedora VM for development but prefer to run tests on Windows, I added the following logic to config/database.php:

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
return array('default' => ..test config..);
} else {
return array('default' => ..normal config..);
}

I did the same thing in bootstrap.php, selectively enabling the unittest module and changing the cache dir based on the OS. You can probably think of some way to do the same in your setup even if you aren’t using a different OS for serving HTTP requests.

2. Creating tests

Tests for application files go into the application/tests/ folder you create.

Tests for any module (inc. application, database, unittest etc.) go in the ‘tests’ folder in the module.

Tests extend Kohana_Unittest_TestCase, not PHPUnit_Framework_TestCase.

To group individual tests into modules you need to add the tests to a group for that module. The convention for group names is modules.{module_name}. This is done using the docblock @group specification (can have multiple group specifications). See Writing Tests in the repo on GitHub.

3. Fixtures

The easiest way to create fixtures is to use setUp() to set the DB, then tearDown() to clean up. You can just use the DB class, or use ORM to add the records. Example:

class WhateverTest extends Kohana_Unittest_TestCase {
   function setUp() {
      // setup the DB
      DB::query(NULL, "TRUNCATE `kohana_test`.`whatever`;")->execute();
      $fixture_rows = array(
          array(
            'key' => 'billing_key',
            'value' => '0',
          ),
      );
      // add the necessary items
      foreach($fixture_rows as $row) {
         DB::insert('whatever', array_keys($row))->values(array_values($row))->execute();
      }
   }
   function tearDown() {
   }

There is probably a better way using a proper fixture class, but I haven’t seen one for Kohana yet.

4. Execution

You will need to specify the path to the KO3 webroot index.php as the bootstrap for phpUnit. This is because the dependencies which your code has need to be loaded by Kohana.

By default, all tests in application/tests/ will be run.

phpunit --bootstrap=index.php ./modules/unittest/tests.php

To run a specific group of tests on the cli add –group {groupname.in.testfile} between the –bootstrap switch and the path to the tests folder.

phpunit --bootstrap=index.php --group groupname.in.testfile ./modules/unittest/tests.php

phpDocumentor

Install phpDocumentor using PEAR: pear install phpDocumentor.

On Windows, if you use paths with spaces in them it is easiest to make sure that phpDocumentor is in the php include path (edit php.ini if necessary), and invoke php while passing the phpdoc file directly (see below for example).

You will probably only want to generate API docs for application/, perhaps selectively adding a few modules. And you probably want to exclude: application/vendor/, application/logs/, application/views/, application/bootstrap.php and application/config/ from the API documentation.

Remember that phpDocumentor does not support multiple option specifications: you have to join the paths with a comma. Furthermore, remember to have the directory separator character at the end of directories to trigger the correct interpretation.

Kohana3 uses a number of custom tags, so you will want to exclude those. Here is a sample run:

php "c:\Program Files (x86)\PEAR\phpdoc" --directory application/
--ignore application/vendor/,application/logs/,application/views/,application/bootstrap.php,application/config/ --target doc/api
--customtags type,default,constructor,chainable,class,namespace,module,group

There are a few options that may also prove useful:
–parseprivate causes private methods to be shown
–output HTML:frames:earthli changes the output to earthli, which I like.
–undocumentedelements Adds warnings to errors.html for undocumented elements.


20
Jul 10

Better web application interface markup: lessons from theme frameworks

Every time I start a new web application project, I spend a while (re)thinking what the layout structure should be in terms of CSS and HTML (e.g. semantic naming, organizing CSS markup).

Recently, I did a project using the Thematic theme framework for WordPress, and that got me thinking about how custom web applications interfaces could be improved. As an individual developer, I rarely have the luxury of focusing on the details of the layout. In contrast, the developers building theme frameworks have spent years thinking about how to create a generic, extensible structure for web application interfaces.

I had a look at Thematic, WP-Framework and Theme Hybrid (see more frameworks in this Smashing Magazine article). I used Thematic, since I ended up using it in the WordPress project I did. Here is how Thematic does it’s HTML layout:

An overview of the HTML code (based on Thematic)

I think the most interest parts of Thematic for web application design are the HTML structure, use of ids and classes and the CSS files. The hook-and-filter system (overview here) is less interesting from the web application point of view, since you will most likely be writing all of the code from scratch for your own web applications.

<!doctype html>
<html>
   <head>
      <title></title>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   </head>
   <body>
      <div id="wrapper">
         <!-- Header -->
         <div id="header">
            <div id="branding">
               <div id="blog-title"><span>...</span></div>
            </div>
            <div id="access">
               <div class="menu">
                  <ul>...</ul>
               </div>
            </div>
         </div>
         <!-- Main -->
         <div id="main">
            <div id="container">
               <div id="content">
                  <div>...</div>
               </div>
            </div>
            <div id="primary">
               <ul>...</ul>
            </div>
         </div>
         <!-- Footer -->
         <div id="footer">
            <div id="siteinfo">...</div>
         </div>
      </div>
   </body>
</html>

1. Body and wrapper

In all of the frameworks, the body classes contain various information such as the platform (windows) and the browser (firefox, ff3).

Benefits:
Easier to specify per-browser css fixes (if needed). This makes it easier to make adjustments based on the browser or OS used in CSS, eg. “.windows xxx.yyy { … }”.

Separation between body and content wrapper. In the framework, the content is wrapped in a wrapper layer (id=wrapper), so that the body has only one child element. I would imagine this is to make it easier to use CSS to adjust the padding and background of the pages.

2. Heading

The heading consists of two sub-divs, as shown below:

There are only three subelements to the content wrapper: heading, main and footer. Each of these divs is full-size and positioned relatively.

Benefits:
Easy to add repeating backgrounds. One can easily apply a CSS background-image to create a repeating and consistent heading.

Easy positioning of the elements. The heading consist of the branding and access divs. This makes it easy to add new heading elements in the branding, while keeping the menu (=access div) separate, with it’s own background.

Standard menu HTML. All of the frameworks appear to be using the Superfish menu by default, which is based on jQuery.

3. Main content

The main content consists of a container with a content-sub-div and a primary sidebar:

The main content div has a fixed size. This is then subdivided into two divs, container (for the content) and primary (for the primary menu block).

Benefits:
Semantic markup; floats to reposition Having these separate divs means that switching from “menu on the right” to menu on the left is simple, since one can change the CSS float directions to reverse the positions of the subcontainers.
Easy to add new content areas One could also add more subdiv for additional content areas, and create multiple columns relatively easily by positioning the subdivs within the main content div.
Easy to add sub-items The primary menu is a div, with each block having its own unordered list (ul). In practice, this leads to two levels of lists – one for the item blocks themselves, the other for the sub-items (e.g. the links in a Archives block).

4. Footer

The footer consists of a single subdiv:

Again, the top-level div only specifies the margin, while the inner divs are positioned within it. This makes it easy to add a background to the footer div.

Does this work?

I’m currently using this approach in recent two web applications I built. The markup seems a lot cleaner, more standard and I have found that re-theming the same basic HTML is much nicer than reinventing the wheel. In short, I think this is a good approach. Let me know if you have improvements via a comment.

Somewhat related: What about other techniques, such as CSS Grid frameworks or Haml/Sass?

I’m not yet convinced that I need to use a CSS Grid framework (e.g. Stackoverflow discussion).

As for Haml, I’m pretty sure that I am fine with regular HTML without any syntactic sugar.

Sass seems to be a real improvement over CSS (variables, nesting, mixins etc.), but on the other hand I haven’t had the time to set it up with my non-Ruby environment.


10
Jul 10

Quick tip: Netbeans is excluding / ignoring directories in version control

Is Netbeans not showing the version control options correctly for some of your directories? Check whether you have accidentally left .svn directories. All source control system directories are hidden by default in Netbeans, which makes spotting this a bit tricky.

Check for whether there is a another repo, e.g. a .svn directory somewhere in your path. Netbeans seems to allow “stacking” source control systems, and will unsuccessfully try to load this versioning data instead of whatever you are using in your repository. This means that you won’t get those pretty markers for modified lines and won’t be able to view history for those files or perform a diff from the Netbeans GUI.

See ~/.netbeans/$version/var/log/messages.log for debugging information. Look for messages related to source control loading failures.

Get rid of the extra directories (e.g. .svn in each directory) in your repo, and delete ~/.netbeans/$version/var/cache/svncache to avoid conflicts with cached incorrect versioning data.


6
Jun 10

Quick tip: how to fix “mysqlnd cannot connect to MySQL 4.1+ using old authentication” onPHP5.3

I recently upgraded to PHP 5.3 on Windows, and ran into this problem:

Warning: mysql_connect(): OK packet 6 bytes shorter than expected in ...
Warning: mysql_connect(): mysqlnd cannot connect to MySQL 4.1+ using old authentication in ... 

I run my Linux servers on VMWare when I do development, so the MySQL database itself was from quite a while ago. As you will see, the core issue here is that MySQL can have passwords with hashes stored in the old 16-character format, which is not supported by PHP 5.3′s new mysqlnd library.

Since I couldn’t find a good solution with a quick Google, here is how I solved this without having to downgrade PHP or MySQL (as some of the solutions suggested):

1. Change MySQL to NOT to use old_passwords

It seems that even MySQL 5.x versions still default to the old password hashes. You need to change this in “my.cnf” (e.g. /etc/my.cnf): remove or comment out the line that says

old_passwords = 1

Restart MySQL. If you don’t, MySQL will keep using the old password format, which will mean that you cannot upgrade the passwords using the builtin PASSWORD() hashing function. You can test this by running:

 SELECT Length(PASSWORD('xyz'));
+-------------------------+
| Length(PASSWORD('xyz')) |
+-------------------------+
|                      16 |
+-------------------------+
1 row in set (0.00 sec)

The old password hashes are 16 characters, the new ones are 41 characters.

2. Change the format of all the passwords in the database to the new format

Connect to the database, and run the following query:

SELECT user,  Length(`Password`) FROM   `mysql`.`user`;

This will show you which passwords are in the old format, ex:

+----------+--------------------+
| user     | Length(`Password`) |
+----------+--------------------+
| root     |                 41 |
| root     |                 16 |
| user2    |                 16 |
| user2    |                 16 |
+----------+--------------------+

Notice here that each user can have multiple rows (one for each different host specification).

To update the password for each user, run the following:

UPDATE mysql.user SET Password = PASSWORD('password') WHERE user = 'username';

Finally, flush privileges:

FLUSH PRIVILEGES;

31
May 10

WordPress 3.0 multi-site multi-domain problems with solutions

Here are some problems I ran into when setting up WordPress 3.0 with multiple different domains, along with how I solved them. I figured it’s better to get them out on the blog now, and update this post if/when I have more solutions.

Problem 1: I can’t load page URLs except when using ?p=xyz permalinks

If you are having problems with page URLs not working on multisite domains, make sure you have:

    AllowOverride FileInfo Options

defined in your httpd.conf for the directory the Virtual host is in.

Problem 2: The new themes I installed and activated on the main WP 3.0 site do not show up on separate domains

The permissions for separate domains do not get automatically updated when you activate themes on the main site.

In MySQL, copy the value for “allowedthemes” in the wp_sitemeta table. This will enable themes on separate domains. Example:

INSERT INTO `wordpress_database`.`wp_sitemeta` (`meta_id`, `site_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'allowedthemes', 'a:2:{s:9:"it-oikeus";b:1;s:9:"twentyten";b:1;}');

where the site_id is the site ID for the separate domain, and meta_value is copied from the first site.

Problem 3: I cannot access Plugins on separate domains

In MySQL, copy the value for “menu_items” in the wp_sitemeta table. This will enable plugins on separate domains. Example:

INSERT INTO `wordpress_database`.`wp_sitemeta` (`meta_id`, `site_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'menu_items', 'a:1:{s:7:"plugins";s:1:"1";}');

where the site_id is the site ID for the separate domain, and menu_items is copied from the first site.

Problem 4: New users are not visible on separate domains

in wp_usermeta, set:

wp_2_capabilities = a:1:{s:13:”administrator”;s:1:”1″;}
wp_2_user_level = 10

(NEW June/2010) Problem 5: Upload files not working on separate domains

Replace site_id (2) with your site id in the query below:

INSERT INTO `wordpress_database`.`wp_sitemeta` (`meta_id`, `site_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'upload_filetypes', 'jpg jpeg png gif midi mid aac ac3 aif aiff m3a m4a m4b mka mp1 mp2 mp3 ogg oga ram wav wma asf avi divx dv flv m4v mkv mov mp4 mpeg mpg mpv ogm ogv qt rm vob wmv doc docx docm dotm odt pages pdf rtf wp wpd numbers ods xls xlsx xlsb xlsm key ppt pptx pptm odp swf asc csv tsv txt bz2 cab dmg gz rar sea sit sqx tar tgz zip css htm html php js
');

23
May 10

Upgrading / moving WordPress 2.9 installs to multisite WP 3.0

In the previous blog post I discussed how the multi-site functionality in WP 3.0 can be enabled and how it can be hacked to use multiple different domains. Now, let’s have a look at how you can migrate all of your old WP 2.9 sites under a single WP3.0 installation.

The basic steps are:

  1. Move posts
  2. Move attached files
  3. Fix URLs in posts
  4. Move/recreate users

1. Moving the posts from WP 2.9 to 3.0

Moving the posts, links, comments and terms to WP 3.0 is quite easy. A table-by-table comparison shows that the main tables have identical structures (comparison between a 2.9.2 site and 3.0 beta 2). This means that we can use SQL for the migration. Alternatively, you could export and import (see the functionality under Tools -> Export / Import).

In WP 3.0, the tables are sharded – each blog has it’s own set of tables, and the id of the blog is used as a part of the table name: for example, wp_2_commentmeta is the wp_commentmeta table for the multisite blog with id=2. Find out what the blog_id for your blog is, and use it in the SQL queries below.

I performed the following queries, which move all the tables EXCEPT the options table. Moving the options table seems to result in a broken blog, since the themes, settings and plugins referenced by WordPress will not exist in the new environment.

TRUNCATE newblog.wp_2_commentmeta;
INSERT INTO newblog.wp_2_commentmeta SELECT * FROM oldblog.wp_commentmeta;

TRUNCATE newblog.wp_2_comments;
INSERT INTO newblog.wp_2_comments SELECT * FROM oldblog.wp_comments;

TRUNCATE newblog.wp_2_links;
INSERT INTO newblog.wp_2_links SELECT * FROM oldblog.wp_links;

TRUNCATE newblog.wp_2_postmeta;
INSERT INTO newblog.wp_2_postmeta SELECT * FROM oldblog.wp_postmeta;

TRUNCATE newblog.wp_2_posts;
INSERT INTO newblog.wp_2_posts SELECT * FROM oldblog.wp_posts;

TRUNCATE newblog.wp_2_terms;
INSERT INTO newblog.wp_2_terms SELECT * FROM oldblog.wp_terms;

TRUNCATE newblog.wp_2_term_relationships;
INSERT INTO newblog.wp_2_term_relationships SELECT * FROM oldblog.wp_term_relationships;

TRUNCATE newblog.wp_2_term_taxonomy;
INSERT INTO newblog.wp_2_term_taxonomy SELECT * FROM oldblog.wp_term_taxonomy;

What if these queries fail?

Some WordPress plugins modify the default tables – which means there are extra fields that need to be ignored. If the INSERT INTO statements below don’t work for you, you need to explicitly state the names of the source and destination fields instead of using *. This means losing the added fields, so you need to think about it on a case-by-case basis. See the MySQL docs for INSERT INTO … SELECT syntax.

2. Moving attached files

WP 3.0 uses a slightly different directory structure for multisite. The files for each blog are under blogs.dir/[blog_id]/, and use the same substructure as before. Hence, the new directory structure is like this:

/wp-content/blogs.dir/[blog_id]/files/...

and if you keep using the same structure (ex. “year/month/filename”) you can just copy the files:

mkdir -p ./wp-content/blogs.dir/[blog_id]/files/
cp -R ./oldblog/wp-content/uploads/* ./newblog/wp-content/blogs.dir/[blog_id]/files/

3. Fixing URLs in posts

Try loading a post with images. The URLs (may) change from:

http://blog.domain.com/wp-content/uploads/2010/04/image.png

to:

http://blog.domain.com/files/2010/04/image.png

You can fix this by performing a replace on the wp_[blog_id]_posts table:

UPDATE wp_2_posts SET guid = replace(guid, "http://oldblog.domain.com/wp-content/uploads", "http://blog.domain.com/files");
UPDATE wp_2_posts SET post_content_filtered = replace(post_content_filtered, "http://oldblog.domain.com/wp-content/uploads", "http://blog.domain.com/files");
UPDATE wp_2_posts SET pinged = replace(pinged, "http://oldblog.domain.com/wp-content/uploads", "http://blog.domain.com/files");
UPDATE wp_2_posts SET post_excerpt = replace(post_excerpt, "http://oldblog.domain.com/wp-content/uploads", "http://blog.domain.com/files");
UPDATE wp_2_posts SET post_content = replace(post_content, "http://oldblog.domain.com/wp-content/uploads", "http://blog.domain.com/files");

If you also changed the domain, you need to fix the links (do this after you fix the file upload paths):

UPDATE wp_2_posts SET guid = replace(guid, "http://oldblog.domain.com", "http://blog.domain.com");
UPDATE wp_2_posts SET post_content_filtered = replace(post_content_filtered, "http://oldblog.domain.com", "http://blog.domain.com");
UPDATE wp_2_posts SET pinged = replace(pinged, "http://oldblog.domain.com", "http://blog.domain.com");
UPDATE wp_2_posts SET post_excerpt = replace(post_excerpt, "http://oldblog.domain.com", "http://blog.domain.com");
UPDATE wp_2_posts SET post_content = replace(post_content, "http://oldblog.domain.com", "http://blog.domain.com");

4. Moving the users

You can either 1) manually create new user accounts for your users, or 2) try to move the user accounts. Most blogs don’t really have many users beyond the admin, since commenting doesn’t need a user account. My recommendation would be to manually add the very few users you have.

If you want to move the users in bulk using SQL, then you will need to take into account that:

  1. In WP 3.0, the wp_users table is shared among all the sites.
  2. The wp_users table in WP 3.0 has two extra fields: spam and deleted.
  3. Each blog site has its own wp_capabilities, wp_user-settings and wp_user-settings_time fields (e.g. wp_2_capabilities) in the wp_users table. The format (serialized PHP arrays) appears to be the same, so you will need to create one user with the rights you want manually, then these three keys for each of the blogs you want to user to be able to access.

I didn’t try this out, since the blogs I have only use a small number of users. I just created individual admin accounts for those blogs that I am not administering personally.