{"id":943,"date":"2012-09-10T15:35:09","date_gmt":"2012-09-10T15:35:09","guid":{"rendered":"http:\/\/blog.soton.ac.uk\/webteam\/?p=943"},"modified":"2012-09-10T15:36:02","modified_gmt":"2012-09-10T15:36:02","slug":"merging-wordpress-multisites","status":"publish","type":"post","link":"https:\/\/blog.soton.ac.uk\/webteam\/2012\/09\/10\/merging-wordpress-multisites\/","title":{"rendered":"Merging WordPress Multisites"},"content":{"rendered":"<p>ECS had a blog server for some years, home to a number of mature blogs.\u00a0 As part of the university-wide systems centralisation, these blogs had to be migrated to existing Southampton WordPress server. Patrick and I were tasked with this.<\/p>\n<p>Our initial googling return very little information about this, other than people saying how hard it was, so we decided that it was well worth documenting.\u00a0 It wasn\u2019t as hard as all that, though we did things that can\u2019t be considered good computer science.<\/p>\n<p>This is presented as a set of instructions, and we\u2019re assuming that there are two multisite installations that need to be moved onto a single new server.\u00a0 It relies on the database structure that wordpress 3.4.1 uses, so if you have a different version, your mileage may vary.<\/p>\n<h3><!--more-->Preliminaries<\/h3>\n<p>You will need a fresh server, with a fresh install of\u00a0Wordpress (verify that it works). \u00a0Don&#8217;t worry too much about plugins and themes, as you will be replacing the whole WordPress tree.<\/p>\n<p>Decide upon which source server will be primary. \u00a0This is almost certainly the one with the most blogs. \u00a0The heavy lifting in this task will be moving the blogs from the secondary server to the primary one. \u00a0Note that it doesn&#8217;t matter how many posts the blogs have, just how many blogs are on the multisite.<\/p>\n<h3>Glossary<\/h3>\n<p>The following terms will be used throughout these instructions:<\/p>\n<p style=\"padding-left: 30px;\"><strong>Primary<\/strong> [Server\/Database\/Tree]<\/p>\n<p style=\"padding-left: 60px;\">The server\/database\/file tree of the WordPress install with the most blogs.<\/p>\n<p style=\"padding-left: 30px;\"><strong>Secondary<\/strong> [Server\/Database\/Tree]<\/p>\n<p style=\"padding-left: 60px;\">The server\/database\/file tree of the WordPress install with the fewest blogs.<\/p>\n<p style=\"padding-left: 30px;\"><strong>Target<\/strong> [Server\/Database]<\/p>\n<p style=\"padding-left: 60px;\">The server\/database onto which the blogs will be moved.<\/p>\n<h3>Package Up the Primary Database and Tree<\/h3>\n<p>On the primary server, cd into the WordPress root directory and dump the database:<\/p>\n<p style=\"padding-left: 30px;\"><code>mysqldump --default-character-set=utf8 --lock-tables=false -c -uUSER -pPASS DBNAME &gt; mysqlbackup.[date].mysqldump<\/code><\/p>\n<p>Then tar up the WordPress tree and scp it across to the target server. \u00a0Note that setting the character set to utf8 is important.<\/p>\n<p>On the target server, create a convenient working directory and untar.<\/p>\n<h3>Manipulate the Primary Database Dump<\/h3>\n<p>Both the base URL and the base path are stored multiple times in the database. \u00a0These will need to be updated. \u00a0The easiest way to do this is to modify it using vim and regular expressions.<\/p>\n<p>In the example below, we were moving from <code>http:\/\/blogs.ecs.soton.ac.uk<\/code> to <code>http:\/\/blog.soton.ac.uk<\/code>. WordPress was located at <code>\/home\/blogs\/blogs.ecs.soton.ac.uk\/htdocs\/<\/code> on the primary server and <code>\/usr\/share\/wordpress\/<\/code> on the target server.<\/p>\n<p style=\"padding-left: 30px;\"><code> :%s\/\\\/home\\\/blogs\\\/blogs\\.ecs\\.soton\\.ac\\.uk\\\/htdocs\/\\\/usr\\\/share\\\/wordpress\/g<br \/>\n:%s\/http:\\\/\\\/blogs\\.ecs\\.soton\\.ac\\.uk\/http:\\\/\\\/blog.soton.ac.uk\/g<br \/>\n:%s\/blogs\\.ecs\\.soton\\.ac\\.uk\/blog.soton.ac.uk\/g<\/code><\/p>\n<h3>Get the Primary Blogs Up<\/h3>\n<p>Move the target WordPress tree to a backup directory and swap in the Primary wordpress tree. \u00a0Then edit wp-config.php:<\/p>\n<ul>\n<li>Change DOMAIN_CURRENT_SITE to the target URL<\/li>\n<li>Make a note of the database information<\/li>\n<\/ul>\n<p>Create a new database using the settings from wp-config.php. On the command line:<\/p>\n<p style=\"padding-left: 30px;\"><code> echo 'CREATE DATABASE dbname;' | mysql<br \/>\necho \"GRANT ALL PRIVILEGES ON dbname.* TO 'dbuser'@'localhost' IDENTIFIED BY 'password';\" | mysql<br \/>\necho \"FLUSH PRIVILEGES;\" | mysql<\/code><\/p>\n<p>Then cat the mysqldump into the new database (don&#8217;t forget the utf8 option):<\/p>\n<p style=\"padding-left: 30px;\"><code>cat \u00a0mysqlbackup.[date].mysqldump | mysql\u00a0--default-character-set=utf8\u00a0-uDBUSER -pPASSWORD DBNAME<\/code><\/p>\n<p>Restart apache, and check that the blogs are all visible.<\/p>\n<h3>Create Blog ID Map and Placeholders for Secondary Blogs<\/h3>\n<p>Each blog has a numeric ID and a path. The IDs are assigned serially, and must be unique. You will need to assign new IDs to all secondary blogs. To get the information out of the database, do the following for the Secondary database (you may want to print this out to note down the new IDs):<\/p>\n<p style=\"padding-left: 30px;\"><code>echo 'select blog_id, path from wp_blogs' | mysql -u USER -pPASSWORD DBNAME<\/code><\/p>\n<p>Next, using the target WordPress&#8217; web interface, log in and create a new blog for every blog returned from the query above. It is <strong>essential<\/strong> that the same path is used.<\/p>\n<p>Finally, run the above query on the target database to get a list of the new blog IDs. You will need to make a note of the mapping between IDs on the secondary server and IDs on the target server.<\/p>\n<h3>Merge in Secondary Users<\/h3>\n<p>The user accounts in the secondary blog need to be moved, and they all have numeric IDs which will need to be incremented. They also have blog permissions associated with them.<\/p>\n<p>First, find the highest ids in the user and usermeta tables in both the target and the secondary databases. The mysql for doing this is:<\/p>\n<p style=\"padding-left: 30px;\"><code>SELECT MAX(ID) FROM wp_users;<br \/>\nSELECT MAX(umeta_id) FROM wp_usermeta;<\/code><\/p>\n<p>Make a note of the highest ID and highest umeta_id (from whichever database has the highest). Round these numbers up to the nearest round number.<\/p>\n<p>Then, dump the wp_users and wp_usermeta tables from the secondary database and cat them into a temporary database under your control (don&#8217;t forget the utf8 option). Run the following commands in the temporary database (using the numbers rounded up above instead of the examples of 50000 and 600000):<br \/>\n<code><\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>UPDATE wp_users SET ID=ID+50000;<br \/>\nUPDATE wp_usermeta SET user_id=user_id+50000;<br \/>\nUPDATE wp_usermeta SET umeta_id=umeta_id+600000;<\/code><\/p>\n<p>Dump the temporary database (note the use of the &#8211;no-create-info argument so as to not drop the tables when inserting):<\/p>\n<p><code> mysqldump --default-character-set=utf8 --no-create-info -u USER -pPASSWORD temp_db wp_usermeta wp_users &gt; user_migration.mysqldump<\/code><\/p>\n<p>&#8230;and then edit it with vim to update the blog permissions. \u00a0For each blog in your ID map, run the following substitution command (in this example, the secondary blog ID of 7 maps to the target blog ID of 88):<\/p>\n<p style=\"padding-left: 30px;\"><code> :%s\/wp_7_\/wp_88_\/g<\/code><\/p>\n<p>This will update all the user permissions in the wp_usermeta table.<\/p>\n<p>Finally, cat this file into the target database. \u00a0This will insert all of the new user records and user metadata into the two tables.<\/p>\n<h3>Merge Plugins and Themes<\/h3>\n<p>Create a copy of the secondary WordPress tree in a convenient location on the target server. \u00a0You will need to diff and merge the following directories in the secondary and target wordpress trees:<\/p>\n<ul>\n<li><code>\/[wordpress_root]\/wp-content\/plugins<\/code><\/li>\n<li><code>\/[wordpress_root]\/wp-content\/mu-plugins<\/code><\/li>\n<li><code>\/[wordpress_root]\/wp-content\/themes<\/code><\/li>\n<\/ul>\n<p>Use <code>diff -rq<\/code> on the directories of the target install and the copy of the secondary install, and copy across anything that&#8217;s missing in the target install. If there are differences, you&#8217;ll have to figure out what to keep. \u00a0Note that plugins and mu-plugins may need to be configured through the target web interface. \u00a0Any themes copied in from the secondary tree will also need to be enabled through the web interface ( My Sites -> Network Admin -> Dashboard -> Themes ).<\/p>\n<h3>Copy Blog Media Files<\/h3>\n<p>Each blog has a directory for uploaded files. \u00a0These need to be copied across. \u00a0Refer to your blog ID map, and copy these directories. \u00a0For example, in our map, the secondary blog ID of 7 maps to the target blog ID of 88. \u00a0This means that the contents of of:<\/p>\n<p style=\"padding-left: 30px;\"><code>\/[secondary_root]\/wp-content\/blogs.dir\/7\/<\/code><\/p>\n<p>&#8230;needs to be copied into a new directory at<\/p>\n<p style=\"padding-left: 30px;\"><code>\/[target_root]\/wp-content\/blogs.dir\/88\/<\/code><\/p>\n<h3>Import Secondary Blog Database Tables<\/h3>\n<p>Each blog has a number of database tables that hold posts, setting and other bits of data. \u00a0A list of them would look something like this:<\/p>\n<ul>\n<li>wp_7_commentmeta<\/li>\n<li>wp_7_comments<\/li>\n<li>wp_7_email<\/li>\n<li>wp_7_links<\/li>\n<li>wp_7_options<\/li>\n<li>wp_7_postmeta<\/li>\n<li>wp_7_posts<\/li>\n<li>wp_7_term_relationships<\/li>\n<li>wp_7_term_taxonomy<\/li>\n<li>wp_7_terms<\/li>\n<\/ul>\n<p>Cat a dump of the secondary database (don&#8217;t forget the utf8 options) into your temporary database (empty it beforehand if necessary). \u00a0Drop all tables from the database that aren&#8217;t blog tables (tables that don&#8217;t start with &#8216;wp_N_&#8217;). \u00a0For each blog ID, run the following mysql command (use the rounded user ID from above):<\/p>\n<p style=\"padding-left: 30px;\"><code>UPDATE wp_7_posts SET post_author = post_author + 50000;<\/code><\/p>\n<p>Dump the database and edit with vim. \u00a0For each ID in your map, run the following (replace the digits according to your blog ID map):<\/p>\n<p style=\"padding-left: 30px;\"><code>:%s\/wp_7_\/wp_84_\/g<\/code><\/p>\n<p><code><\/code>Save the file and exit vim, then cat the file (which must only contain blog tables &#8212; verifying this may be a good idea) into the target database.<\/p>\n<h3>Verify the Front Page<\/h3>\n<p>If you wish to use the front page of the secondary multisite as the frontpage of the target multisite, then you should set this up by hand. \u00a0Open both in your web browser and copy across the settings.<\/p>\n<h3>Configure Apache Redirects<\/h3>\n<p>You will want to maintain any old URLs. \u00a0In your apache configuration you will need:<\/p>\n<p style=\"padding-left: 30px;\"><code> RewriteEngine On<br \/>\nRewriteCond %{HTTP_HOST} ^(oldblogurl.domain.com) [NC]<br \/>\nRewriteRule ^(.*)$ http:\/\/newblogurl.domain.com$1 [R=301,L]<\/code><\/p>\n<p><code><\/code>Then update your DNS entries.<\/p>\n<p>Job Done!<\/p>\n<h3>A Final Word<\/h3>\n<p>We noticed that some (but not all) theme settings had not moved across and had to be set manually. \u00a0It may be a good idea to inspect every blog for issues after the migration, particularly those from the secondary server.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>ECS had a blog server for some years, home to a number of mature blogs.\u00a0 As part of the university-wide systems centralisation, these blogs had to be migrated to existing Southampton WordPress server. Patrick and I were tasked with this. Our initial googling return very little information about this, other than people saying how hard [&hellip;]<\/p>\n","protected":false},"author":24,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[52475],"tags":[],"class_list":["post-943","post","type-post","status-publish","format-standard","hentry","category-wordpress-2"],"_links":{"self":[{"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/posts\/943","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/users\/24"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/comments?post=943"}],"version-history":[{"count":9,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/posts\/943\/revisions"}],"predecessor-version":[{"id":952,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/posts\/943\/revisions\/952"}],"wp:attachment":[{"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/media?parent=943"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/categories?post=943"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.soton.ac.uk\/webteam\/wp-json\/wp\/v2\/tags?post=943"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}