Migrating Membership Site from Drupal to WordPress PMPro
At first glance I thought this project would be straightforward. Just move the membership data from one database to another.
As I dug in deeper, I realized it would be quite challenging.
This post will document the process.
First, I analyzed the Drupal database to get an understanding of the user registration process and fields collected and stored. A second database was being maintained on the owner’s PC. The Drupal database contained the user’s registration data, but the second database was being used to manage annual payment and additional information about the user. The intent was to eventually combine the two so everything could be handled in one system.
After careful analysis, it was determined that the local (second) database had the most current information such as the user’s address plus it had all of the payment and registration information, which the Drupal database didn’t have.
So, now the project became a transfer of data from a MicroSoft Access database to a WordPress database. The idea of merging the Access database and Drupal database was considered, however, the only field common between the two was the email address. Even first name and last name differed for some users between the two databases. Additionally, there were no fields of any importance stored for the user in Drupal that were not in Access.
I chose Paid Memberships Pro because I had had some limited experience with it on another project. With the other project, it seems like a good plugin and seemed to have good support. I opted for the paid support since I needed access to the Support Forum and code examples. I could tell that there would be custom programming and that I would need the extra support.
I created a WordPress installation, added the PMPro plugin, and got to work. The first step was to determine the membership levels. There were going to be two main levels that users could choose from, and three levels that would be hidden from users, and used only by admins. For example, there is a Member-for-Life level that is free and can only be assigned by an admin. Once members achieve the requirements of that level, the admin changes their level.
The existing system for membership involved payment by check, and in a few rare cases, members paid by Paypal, but it was a manual process as far as keeping track of who paid and how much they paid.
The plan for the new system is to start out offering only payment by check, and then soon after, add online payment via Paypal.
There are two plugins for paying by check:
Pay by Check Addon – can set for each membership level whether that level accepts payment by check only, credit card only, or both; it marks the order as “pending” and users won’t have access until an admin changes the order status to “success”. However, they are still considered “members” in the system, and so they show up in the members list and some custom code, themes, and other addons may give them “access” to things since they are a member.
Check Payment Level – requires adding two versions of each membership level, ex:
- Regular Member – pay by check
- Regular Member – pay via Paypal
So, I chose the Pay by Check Addon since it didn’t require duplicate levels. I installed the plugin and completed a few additional setup steps for installation.
Next, looking at the Access database, I saw there were many user fields that we planned to keep besides the usual ones that WordPress stores for each user, such as Spouse’s Name, Type of Business, etc. I found out that in order to add these fields to the database, I would need to use an addon called Register Helper. There was mention in the forums of using other tools to add the fields, but by using Register Helper, there are benefits that the other tools didn’t provide.
I learned that I would need to specify for each field whether it would appear:
- in the user’s profile
- on the checkout page, meaning it would be populated as the user registers and pays
Additionally, I can specify whether the field is for admins only to view and edit, or for the user to view in their profile and edit. May need to add a code snippet in order to display the admin-only fields in the admin panel. Also need to add code to display the additional fields in the Member Listing.
Other aspects that I can set for each field include: read-only, field label, hint, and required. If you want the field to be included in an Export to CSV file, then be sure to specify that for each field (which I did, memberslistcsv=true). I also read about an add-on that makes life easier when adding members manually – Add Member Admin Addon. The admin can create the user, fill in the membership settings, and complete the order in one step. If you want the fields you’re adding to be accessible to this addon, then specify that for each field (addmember=true).
To add the users to the database, I needed to create a CSV file containing the user data. Two plugins are used to import the user data using the CSV file: Import Users from CSV, and Paid Memberships Pro – Import Users from CSV Add On. There is a template to get you started that lists all of the fields required. The import process will assign a membership level to each user, and will maintain any existing subscription information for each user’s membership. For example, you can fill in the amount that their subscription costs, when their membership started, and whether they are an active member or not. There are also a set of fields containing billing information such as billing address.
Next I created a cheat sheet listing all the fields for the new database. I had to cross-reference the field names from the example CSV worksheet with the Access database. I realized I would need to do some data manipulation in order to get the old fields into the new fields, for example:
- user_login, which is the username that the member logs in with – this data did not exist in the Access database, so I created a formula that concatenates three fields – first name, middle initial, last name into a single field – username
- membership_id, which is a number that correlates to the membership level, so for example, a ‘Regular’ member, designated in the Access database as ‘Regular’ would have a membership_id of 1. A find/replace was all that was needed to change the word to a number.
- membership_startdate, which is ‘Date Joined’ in the Access database, needed to be converted from YYYY/MM/DD to YYYY-MM-DD, which is the format that the CSV import requires; some dates contained just the year, so they were converted also to YYYY-01-01.
So, thinking ahead, the next steps would be:
- Add the Register Helper code to the site so that new fields would be created
- Get data for several users from the Access database (in Excel format)
- Perform the data manipulations and change the field names to create the CSV for import.
- Import the users.
So, I submitted my Register Helper code to the experts at PMPro to look over. I’m still wondering about last_name and first_name. They are WordPress core fields, but from what I read on the forums, if you add them via Register Helper, they will not become one with the core fields, they will be their own separate fields. So, you will end up with two copies of first name and last name. And it sounds difficult if not impossible to keep them synched, for example, if the user edits the first name field, it won’t update the other first name field. Same for the billing fields, called pmpro_bfirstname and pmpro_blastname. My decision for now is to go ahead and add first_name and last_name.
Through the support forum, I also learned the answer to one of my other questions which was: what if you use Register Helper to add fields, then find out you want to remove or add fields, or change the field type? Do you have to somehow remove the fields you decided you don’t want? Turns out that the definitions are read and compiled each time the page is loaded. So if you change a text field to a textarea field, the data in that field will stay, but if you change a textarea (300-character) field to a text field (40 characters), then any characters beyond the 40 will be lost.
Yet another question I had was about “profile”=>”admins” in the PMProRH_Field array. I had seen in the forums documentation stating “profile”=>”admins_only”, “profile”=>”admins”, and “profile”=>”admin”, so I wondered which one, or if all usages were correct. It turns out that as long as ‘admin’ is part of the string, it will work.
So, I posted to the forum and got one of the experts to review my pmpro-customizations.php file and got some good tips. I have two sections of code in my file. The first section creates the fields that are for admins only and go into the user’s profile. The second section are all the fields that are collected during checkout and are shown in the user profile and editable by the user. Each section requires a call to pmprorh_add_registration_field, however, the $fields array needs to be reset inbetween.
I uploaded my pmpro-customizations.php file to a folder I created called pmpro-customizations and activated the pluging. Error! It appears that the Register Helper Example plugin must be de-activated or it will clash with my customizations plugin since they both call my_pmprorh_init. So, I de-activated the Register Helper Example plugin, activated PMPro Customizations and it worked!
Next I checked my user profile (I’m the only user so far) and I see all of the new fields.
I did an Export to CSV but none of the new fields were there. I’ll come back to that later.
I have finished adding all of the membership_ columns to the spreadsheet of member data and I will export the first six rows of user data into a MSDOS CSV file. I decided to set the membership enddate to 12/31/2016, and I may just need to manually adjust the start dates. I don’t have anything for the membership_subscription_transaction_id column, which is supposed to be required in order to continue existing subscriptions. I changed the actual email addresses to fake ones just in case, to be sure those users wouldn’t get an email notification.
I added the bank_deposit field to my customizations field and FTP’d it over to the plugin directory. Do I need to deactivate and re-activate the plugin? So that’s what I tried, and sure enough, bank_deposit now appears in the user profile.
Make sure to remove any commas that are in the cells or else it will confuse the comma-separated fields.
I ran the import just by going to Users > Import from CSV. I got an error message that one user was skipped because the email address was the same as an existing email address, which happened to be my user record. I checked the user profiles and the data was all there. Membership Level, however, was ‘none’. I checked in phpmyadmin, and saw that the membership level looked correct there. Quick check of the CSV file and I found the culprit – an extra space at the end of the column label ‘membership_id’. Removed that, re-imported, and then the Membership Levels were filled in.
Next I checked Memberships > Member List and saw that the column ‘Joined’ was today’s date for every user I imported. After some reading on the forum, I found out that Joined is actually the date that the user registered on the website, which may or may not be the same date that they became a member.
Now I need to figure out how to add links that logged in users see to access their profile and billing info. Theme My Login seems to be the one recommended for this.
Worked on the Member Directory. Just took existing Directory page that was there and expanded the shortcode to include more fields. It does list a Pending member who paid by check but check hasn’t been received. Found some code on the forum but it didn’t work. This was eventually resolved. Jason patched the code I had in my customizations file since the member directory add-on for some reason that did have the correction hadn’t been published yet. He said I could safely update to the next version of the member directory add-on when it came out.
Next is the Business Directory, which should list only those members who indicated they wanted to be listed there. The field is called business_roster and will contain ‘yes’ if they want to be shown in the directory. After posting on the forum, I received code that I placed in /themes/affna/paid-memberships-pro/pmpro-member-directory/directory.php. It was placed under the themes folder so it won’t get overwritten when the pmpro software updates.
As far as preventing Pending members from accessing member-only content, I found some code on the support forum that creates a shortcode that works like the [membership] shortcode, but it additionally excludes any pending members.
I need to figure out how to put links in the sidebar for members only. I can do it using the shortcode mentioned above, and then put in the links in HTML, but that won’t work for the section of links that the administrative person will be maintaining. She won’t want to be copying/pasting HTML code into a small window. I need a way for her to build a list of links using a Custom Menu, but how do I keep that custom menu from being displayed to non-members? I tried using the Nav Menus Add-on, which worked great to build a member-only and non-member main navigation menu, but didn’t work in the sidebar. I ended up using the widget-logic plugin. Just added is_user_logged_in() to the widget. UPDATE: Had to change the widget sidebar custom menu because widget logic was not keeping the pending members out. Also pending members could see the Members main nav menu and could see the post topics but not the post content. So, ended up creating two new levels – Apply for Regular and Apply for Associate. These levels had a setting excluding them from nav content.
So I want to collect data that the user enters in upon checkout, but is not saved in the database. These fields are only used to determine whether to allow the person to have an account on the site. The fields will need to be collected and put into the registration email. It seems that in order to do this, I have to modify the checkout template and then also the email template. I just had a look at the checkout template and am thinking I don’t want to edit this. I saw a software update announcement that said to make sure if you’re using your own checkout template, to make sure it still works after applying the update. I just want to make this an easy-to-maintain site and don’t want to have to re-examine custom templates whenever there’s an upgrade. Since it’s only 5 or so fields, I think I will just store them in the database. So I added those fields to the customization plugin and found out that the checkout email that goes to the admin automatically has the user profile fields included! Except — it did not include the additional 5 or so fields because I had them as admin-only, so I changed them to user-profile=true, and now they are in the email. I don’t have to create and edit the email template. BTW, there is an add-on for email templates that lets you edit portions of the email. I was able to correct some text that is in the checkout email to the user.
I noticed that the user profile doesn’t show the billing fields (billing address, etc.). Found a code recipe that will show them in the user profile and so I added that to customizations plugin. For the user that I am testing with, who applied for a membership, there is no info in those billing fields. I want the billing fields to contain the home address info. This is fine for the users I’m importing, because I have those fields filled in. For users who register though, I want to ask for home address and have it go into billing fields. Right now I don’t ask for billing or home address during checkout.
Found code and added it to customizations plugin that removes old WordPress core user profile fields Jabber, AIM, Yahoo IM, and biographical data.
To keep user on the frontend, I used Theme-my-login plugin, and under User Links, you can remove the Dashboard link and Profile link (according to role). Now the Login Widget will not show those links. The member can edit their profile via a link in the sidebar to their Account page.
I gave up on finding a way to automate moving the blog posts from Drupal to WordPress and moved them manually. To assist in moving the photo galleries, I used the Add From Server plugin, which you can point to any folder on your server and tell it to import the files from there.
Actual import of real data: over 2,000 records; import stopped and resulted in a 404 error. I checked and there are only 800 users. Some users had no membership level. Found out that if the record has ‘inactive’, then the membership level is changed to 0. Removed the first 800 records from the CSV, saved the file, and ran the import again. This time over 600 users were imported. Repeated until all records were imported.