Sunday, October 20, 2019

Drupal 7 / Ubercart 7 - How to Migrate Users from Drupal 6 to Drupal 7 (With Maximum Data Preservation)

Drupal core 7.67 / Ubercart 7.x-3.13 / Stability Theme

Following years of frustratingly hard-to-reach, disinterested and/or incompetent and needlessly expensive Drupal developers we have set up an affordable commercial service for Small and Medium sized Enterprise (SME) decision-makers who rely on Drupal to support their business, like us.  So, if you need help to migrate your Drupal 6 users into a Drupal 7, 8, or 9 System, or any other form of Drupal Wizardry, feel free to contact us.  

Our new division, Drupal Wizard provides support for all versions of Drupal.  

We offer the following services to serve the unique and evolving needs of your business:
  • 24/7 Emergency Support
  • Low-Cost Annual Support Packages
  • Drupal Module Integration and Development
  • Linux and Drupal Scheduled Systems Maintenance
Visit www.drupalwizard.com for more information.

How to Migrate Users from Drupal 6 to Drupal 7 (With Maximum Data Preservation)

Migrating user information across a major Drupal boundary (like 6 to 7) is a major hassle because of the state of the technology.  At the time of the release of the development and release of Drupal 7, the message was "Just re-implement your website", obviously a big deal for sites that have hundreds of products and thousands of users like us.

So, we just didn't upgrade...for years...


Now, because we want our customers to be able to use their mobile phone, we have decided to upgrade to the next version of Drupal up from our current version in the expectation that after over a half decade of bug-testing and user contributions, we have a good chance to quickly move from Drupal 6 to Drupal 7 - and this has been largely true.

The initial site development took about a week.  Loading hundreds of products into the new site, installing tweaks and performing optimizations have taken another two weeks or so of work.  

The good news?  In less than a month our new site (mobile.holisticpethelp.com) is live and doing daily business with new customers that we are attracting via Social Media advertisements.  So far, so delighted.

We plan to run the legacy site in parallel on our internal network for some time (in a container - the subject of another, future post) so we can go ahead and access it for reference purposes, but we do not intend to allow our customers to go back to the site once the mobile site is production ready.

Now, on to the dreaded migration.

With a newly implemented and modernized Mobile-ready website, we want to bring in our legacy users from our old Drupal 6 site, ideally in such a way that they supply their username and password to the new site and simply see an updated interface with all of their information migrated over.

This problem comes with the following distinct challenges:

A) Migrate as much user information as possible from the Drupal 6 System into the Drupal 7 System, without messing up what is already in there. 

B) Migrate as much address information as possible from the Drupal 6 System into the Drupal 7 System, without messing up what is already in there

C) Migrate as much order information as possible from the Drupal 6 System into the Drupal 7 System, without messing up what is already in there

D) Activate the migrated users via email to visit the new website, select a "better" password and get them somewhat used to the new interface

E) Redirect all incoming traffic going to the old website to the new website

Migrating user information is possible between Drupal 6 and Drupal 7, but not something that anyone other than a fairly experienced developer could do successfully.  

To accomplish this, the project lead needs to understand how LinuxPHP and SQL works, as well as a fairly intimate understanding of the Drupal System as it evolved over time.  

These are definitely not skills that your typical website owner is likely to possess, unless they were a developer first, like we are.

Anyways, after some investigation under the hood, we have discovered that a lot of user information can be carefully migrated (Name, Email, Id) but other information simply cannot be (Password).  


Furthermore, we have determined that some information is Critical, some is Important, and some is Optional, with another set of information being Irrelevant.

In terms of a priority list, the following seemed reasonable:


The TEST

Before going all crazy with migrating Drupal 6 users to Drupal 7 (with data), I thought it would be prudent to move one user across manually, to see if introducing data into Drupal from the back-end would break anything.

So, let's experiment with moving one identity between the two sites:

> select name from users where name like "Graham Leach";
+--------------+
| name         |
+--------------+
| GL           |
+--------------+

OK, we have an identity in Drupal 6 that needs to be migrated to Drupal 7.

Let's try to move this identity into Drupal 7 using SQL and see what happens.

>select * from drupal_users where name like "GL%";

| 2769 | GL              | c62d929e7b7e7b6165923a5dfc60cb56 | graham@leach.com          |    0 |    0 |         0 |       |           |                0 | 1442730945 | 1493677257 | 1493677256 |      1 | NULL     | en       |         | graham.r.leach@gmail.com  

There's a lot of information in this row, so let's take a shot at decoding it, shall we:

First of all, here's the table schema for the users table in the Drupal 6 System:

+------------------+------------------+------+-----+---------+----------------+
| Field            | Type             | Null | Key | Default | Extra          |
+------------------+------------------+------+-----+---------+----------------+
| uid              | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name             | varchar(60)      | NO   | UNI |         |                |
| pass             | varchar(32)      | NO   |     |         |                |
| mail             | varchar(64)      | YES  | MUL |         |                |
| mode             | tinyint(4)       | NO   |     | 0       |                |
| sort             | tinyint(4)       | YES  |     | 0       |                |
| threshold        | tinyint(4)       | YES  |     | 0       |                |
| theme            | varchar(255)     | NO   |     |         |                |
| signature        | varchar(255)     | NO   |     |         |                |
| signature_format | smallint(6)      | NO   |     | 0       |                |
| created          | int(11)          | NO   | MUL | 0       |                |
| access           | int(11)          | NO   | MUL | 0       |                |
| login            | int(11)          | NO   |     | 0       |                |
| status           | tinyint(4)       | NO   |     | 0       |                |
| timezone         | varchar(8)       | YES  |     | NULL    |                |
| language         | varchar(12)      | NO   |     |         |                |
| picture          | varchar(255)     | NO   |     |         |                |
| init             | varchar(64)      | YES  |     |         |                |
| data             | longtext         | YES  |     | NULL    |                |
| timezone_name    | varchar(50)      | NO   |     |         |                |
+------------------+------------------+------+-----+---------+----------------+

Here's a field-by-field examination of the Drupal 6 Sytem users table:

uid
Importance:   Critical
Meaning:       The unique identifier of this identity in the Drupal 6 System
Note:              This value cannot clash with any existing uid in the Drupal 7 System
DrupalAPI:      Primary Key: Unique user ID

name
Importance:   Critical
Meaning:       The common name of the user, what they use to log in as their "Username"
Note:              This value cannot clash with any existing name in the Drupal 7 System
DrupalAPI:     Unique user name

pass
Importance:   Critical
Meaning:       The password the User supplies to log into the Drupal System
Note:              Unfortunately, this value cannot be migrated due to a password philosophy 
                       change that occurred from Drupal 6 (MD5) to Drupal 7 (SHA512)
DrupalAPI:     User's password (md5 hash)

mail
Importance:   Critical
Meaning:       The current email of the User
Note:              This value is implicated in many verification and notification processes
DrupalAPI:     User's email address

mode
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:    Per-user comment display mode (threaded vs. flat), used 
          by the {comment} module

sort
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:    Per-user comment sort order (newest vs. oldest first), 
          used by the {comment} module

threshold
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:    Previously used by the {comment} module for per-user
          preferences; no longer used.',

theme
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:     User's default theme

signature
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:     User's signature

signature_format
Importance:   irrelevant
Meaning:       
Note:              
DrupalAPI:     The {filter_formats}.format of the signature

created
Importance:   Important
Meaning:       The UNIX-formatted creation date of this account
Note:              
DrupalAPI:     Timestamp for when user was created

access
Importance:   Important
Meaning:       The UNIX-formatted last access date of this account
Note:              
DrupalAPI:     Timestamp for previous time user accessed the site

login
Importance:   Important
Meaning:       The UNIX-formatted last login of this account
Note:              This seems redundant, because you can infer it from the value of access
DrupalAPI:     Timestamp for user's last login

status
Importance:   Critical
Format:          Identical
Meaning:       Whether or not this account is enabled or disabled
Note:              
DrupalAPI:     Whether the user is active(1) or blocked(0)

timezone
Importance:   Important
Meaning:       
Note:              
DrupalAPI:     User's timezone

language
Importance:   Critical
Meaning:       The preferred language context of the client
Note:              in our case the VAST MAJORITY of our customers prefer Traditional Chinese
                      (zh-hant).
DrupalAPI:     User's default language

picture
Importance:   Irrelevant
Meaning:       
Note:              
DrupalAPI:     Path to the user's uploaded picture

init
Importance:   Important
Meaning:       This is the original email address used to initially activate the account
Note:              This might be used as a "fallback" email address in the event that the
                       current email address (mail) somehow goes bad.  Maybe this is for 
                       fault tolerance, maybe not.
DrupalAPI:     Email address used for initial account creation

data
Importance:   Irrelevant
Meaning:       This was originally where meta-data about the user was held.  Deprecated.
Note:              A legacy value that should not be used in the future, it used to hold a bunch
                      of information about the user (shipping information, for example) that now 
                      should be stored elsewhere.
DrupalAPI:     A serialized array of name value pairs that are related
          to the user. Any form values posted during user edit are
          stored and are loaded into a $user object during 
          user_load(). Use of this field is discouraged and 
          it will likely disappear in a future version 
          of Drupal

timezone_name
Importance:   Important
Meaning:       
Note:              
DrupalAPI:    NOT PRESENT IN API

Curious, I took a look at timezone_name and found that it was blank throughout the users table.  Besides that, I think that it should not be in the users table.  This kind of information belongs in a lookup table.

Now that we know what the columns mean, let's map the information provided by the SELECT statement, to see the disposition of the data when mapped onto the schema:

2769                             | uid
GL                               | name
c62d929e7b7e7b6165923a5dfc60cb56 | pass
graham@leach.com                 | mail
0                                | mode
0                                | sort
0                                | threshold
                                 | theme
                                 | signature
0                                | signature_format
1442730945                       | created
1493677257                       | access
1493677256                       | login
1                                | status
NULL                             | timezone
en                               | language
                                 | picture
graham.r.leach@gmail.com         | init
                                 | data
                                 | timezone_name

Password Problems

The first obstacle I hit was the fact that the password scheme has been changed between the Drupal 6 System (MD5) and the Drupal 7 System (SHA512).  This means that transporting the password over between the two systems is impossible, so I could never have just pushed data into the Drupal 7 System and then logged in as I would have normally with the pre-existing Drupal 6 System.

This is because of the way passwords work in Drupal:

1.  Get username & password
2.  Check for username in users table, if found continue, if missing abort

3.  Perform SHA512 operation on supplied password ("hash"), compare to stored password
4.  If they match, let the user in, if not abort.

Seeing as the Drupal 6 System only stores the result of the ("hash") and not the actual password, and the two versions use a different ("hash") mechanism, it is impossible to migrate passwords between the two systems.

Luckily, I know how to spur a user to refresh their password using the One Time Password Module, so this mechanism can be used to reset the missing password from the Drupal 7 System Administrative Interface (People).  This process is explained in my post entitled:


Drupal 7 / Ubercart 7 - Bulk Password Refresh with One Time Login Module

So the final challenge was to create a SQL statement that the Drupal 7 System database server would find acceptable, containing as much of the information as possible drawn from the Drupal 6 System database server.

OK, so we now know what matters and what is coming out of the Drupal 6 System users table, but like a lock & key, the information coming in to the Drupal 7 System users table needs to conform to its format, or there will be errors.

Here's the format of the Drupal 7 users table:

> show columns from users;
+------------------+------------------+------+-----+---------+-------+
| Field            | Type             | Null | Key | Default | Extra |
+------------------+------------------+------+-----+---------+-------+
| uid              | int(10) unsigned | NO   | PRI | 0       |       |
| name             | varchar(60)      | NO   | UNI |         |       |
| pass             | varchar(128)     | NO   |     |         |       |
| mail             | varchar(254)     | YES  | MUL |         |       |
| theme            | varchar(255)     | NO   |     |         |       |
| signature        | varchar(255)     | NO   |     |         |       |
| signature_format | varchar(255)     | YES  |     | NULL    |       |
| created          | int(11)          | NO   | MUL | 0       |       |
| access           | int(11)          | NO   | MUL | 0       |       |
| login            | int(11)          | NO   |     | 0       |       |
| status           | tinyint(4)       | NO   |     | 0       |       |
| timezone         | varchar(32)      | YES  |     | NULL    |       |
| language         | varchar(12)      | NO   |     |         |       |
| picture          | int(11)          | NO   | MUL | 0       |       |
| init             | varchar(254)     | YES  |     |         |       |
| data             | longblob         | YES  |     | NULL    |       |
| uuid             | char(36)         | NO   | MUL |         |       |
+------------------+------------------+------+-----+---------+-------+

Here's a field-by-field examination of the Drupal 7 Sytem users table:

uid
Importance:   Critical
Format:          Identical
Meaning:       The unique identifier of this identity in the Drupal 7 System
Note:              This value cannot clash with any incoming uid from the Drupal 6 System
DrupalAPI:      Primary Key: Unique user ID

name
Importance:   Critical
Format:          Identical
Meaning:       The common name of the user, what they use to log in as their "Username"
Note:              This value cannot clash with any incoming name from the Drupal 6 System
DrupalAPI:     Unique user name

pass
Importance:   Critical
Format:          DIFFERENT [varchar(32) -> varchar(128)]
Meaning:       The password the User supplies to log into the Drupal System
Note:              In the Drupal 7 System this is now a SHA512 value
DrupalAPI:     User's password (SHA512 hash)

mail
Importance:   Critical
Format:          DIFFERENT [varchar(64) -> varchar(254)]
Meaning:       The current email of the User
Note:              This value is implicated in many verification and notification processes
DrupalAPI:     User's email address

mode
Importance:   Irrelevant
Format:          NO LONGER PRESENT
Meaning:       
Note:              
DrupalAPI:    Per-user comment display mode (threaded vs. flat), used 
          by the {comment} module

sort
Importance:   Irrelevant
Format:          NO LONGER PRESENT
Meaning:       
Note:              
DrupalAPI:    Per-user comment sort order (newest vs. oldest first), 
          used by the {comment} module

threshold
Importance:   Irrelevant
Format:          NO LONGER PRESENT
Meaning:       
Note:              
DrupalAPI:    Previously used by the {comment} module for per-user
          preferences; no longer used.',

theme
Importance:   Irrelevant
Format:          Identical
Meaning:       
Note:              
DrupalAPI:     User's default theme

signature
Importance:   Irrelevant
Format:          Identical
Meaning:       
Note:              
DrupalAPI:     User's signature

signature_format
Importance:   irrelevant
Format:          DIFFERENT [smallint(6) -> varchar(255)]
Meaning:       
Note:              
DrupalAPI:     The {filter_formats}.format of the signature

created
Importance:   Important
Format:          Identical
Meaning:       The UNIX-formatted creation date of this account
Note:              
DrupalAPI:     Timestamp for when user was created

access
Importance:   Important
Format:          Identical
Meaning:       The UNIX-formatted last access date of this account
Note:              
DrupalAPI:     Timestamp for previous time user accessed the site

login
Importance:   Important
Format:          Identical
Meaning:       The UNIX-formatted last login of this account
Note:              This seems redundant, because you can infer it from the value of access
DrupalAPI:     Timestamp for user's last login

status
Importance:   Critical
Meaning:       Whether or not this account is enabled or disabled
Note:              
DrupalAPI:     Whether the user is active(1) or blocked(0)

timezone
Importance:   Important
Format:          DIFFERENT [varchar(8) -> varchar(32)]
Meaning:       
Note:              
DrupalAPI:     User's timezone

language
Importance:   Critical
Format:          Identical
Meaning:       The preferred language context of the client
Note:              in our case the VAST MAJORITY of our customers prefer Traditional Chinese
                      (zh-hant).
DrupalAPI:     User's default language

picture
Importance:   Irrelevant
Format:          DIFFERENT [varchar(255) -> int(11)]
Meaning:       
Note:              
DrupalAPI:     Path to the user's uploaded picture

init
Importance:   Important
Format:          DIFFERENT [varchar(64) -> varchar(254)]
Meaning:       This is the original email address used to initially activate the account
Note:              This might be used as a "fallback" email address in the event that the
                       current email address (mail) somehow goes bad.  Maybe this is for 
                       fault tolerance, maybe not.
DrupalAPI:     Email address used for initial account creation

data
Importance:   Irrelevant
Format:          DIFFERENT [longtext -> longblob]
Meaning:       This was originally where meta-data about the user was held.  Deprecated.
Note:              A legacy value that should not be used in the future, it used to hold a bunch
                      of information about the user (shipping information, for example) that now 
                      should be stored elsewhere.
DrupalAPI:     A serialized array of name value pairs that are related
          to the user. Any form values posted during user edit are
          stored and are loaded into a $user object during 
          user_load(). Use of this field is discouraged and 
          it will likely disappear in a future version 
          of Drupal

uuid
Importance:   Critical
Format:          NEW [char(36)]
Meaning:       Universally Unique ID
Note:              This is normally a generated value, so we need to look into this more closely.
DrupalAPI:    NOT PRESENT IN API

It also looked to me like the Drupal 6 System database had a bunch of extra fields that the Drupal 7 System database either doesn't need, or needs to be stored elsewhere than the users table.  This is a sign of the growing pains that Drupal experienced as it scaled up and realized that it needed to segregate certain types of data (longblob, for example) away from being stored in the users table.  This story is not over because I see fields in the Drupal 7 System database schema that really shouldn't be there.  

| mode             | tinyint(4)       | NO   |     | 0       |                |
| sort             | tinyint(4)       | YES  |     | 0       |                |
| threshold        | tinyint(4)       | YES  |     | 0       |                |


N.B.  For those interested in database design, look into Third Normal Form database design, which I studied in 1995 and which still serves me well, even up to today.

A TEST With Hand Crafted SQL

Using the information I had gathered about the Drupal 6 System users table schma and the Drupal 7 System users table schema, I was able to hand-craft a test SQL statement to migrate one user from the Drupal 6 System into the Drupal 7 System, using the GL identity as my example.

Here's the SQL statement I wrote:

INSERT INTO users 
(
uid,
name,
pass,
mail,
theme,
signature,
signature_format,
created,
access,
login,
status,
timezone,
language,
picture,
init,
data,
uuid 
)
VALUES 
(
"2769",                     /* uid              - critical    */
"GL",                       /* name             - critical    */
"",                         /* pass             - impossible  */
"graham@leach.com",         /* mail             - critical    */
"",                         /* theme            - irrelevant  */
"",                         /* signature        - irrelevant  */
"0",                        /* signature_format - irrelevant  */
"1442730945",               /* created          - important   */
"1493677257",               /* access           - important   */
"1493677256",               /* login            - important   */
"1",                        /* status           - critical    */
"NULL",                     /* timezone         - important   */
"en",                       /* language         - critical    */
"",                         /* picture          - irrelevant  */
"graham.r.leach@gmail.com", /* init             - important   */
"",                         /* data             - irrelevant  */
""                          /* UUID             - important?  */
);

UID Clash Avoidance

To prevent problems, it is important that every uid that we manually insert into the Drupal 7 System database be different from any that are already there.  This is because, if there are two users with the same uid, major problems will ensue and the Drupal 7 System will almost certainly crash.

Here's the range of uid from the Drupal 6 System users table:

mysql> select max(uid) from users;
+----------+
| max(uid) |
+----------+
|     7990 |
+----------+

Here's the range of uid from the Drupal 7 System users table:

MariaDB [hph]> select uid from users;
+-------+
| uid   |
+-------+
|     0 |
| 20806 |
| 20810 |
| 20785 |
| 20691 |
| 20809 |
| 20773 |
| 20813 |
| 20818 |
|     1 |
| 20826 |
+-------+

As we can see, there are two special, low-value uid and then a bunch of uid in the 20,000++ range.  

Who Has uid 0?


In the Drupal System, the user with uid 0 is the Anonymous User.

Who Has uid 1?

In the Drupal System, the user with uid 1 is the Administrator

Any migration tool will need to skip those special low-value uid when exporting, so the very same users who are already active in the target system are not interfered with when the migration occurs.

Aside from that small caveat, everything looks OK.  The users who will be coming in from the Drupal 6 System user table all have a uid far lower than the lowest Drupal 7 System uid value.

Name Clash Avoidance

Another thing to keep in mind is that the value for name must also be kept unique within the Drupal 7 System database, so when we perform the migration, we must avoid introducing a duplicate value into the database with anything other than a unique value, or risk corrupting or crashing the Drupal 7 System.  This can be done programmatically, so we will avoid this problem in code, as opposed to taking action in the database.  

This is also a great argument for an intermediate file where an offending name can be manually altered and the implicated user alerted to the change in a graceful way.

UUID



Potential trouble ahead there!

This is a new field and, apparently, not part of the official API specification

Consequently, this field makes me very nervous because I do not know where it comes from, how it works or how it is used in the Drupal 7 System.  UUID's are normally utilized to uniquely identify entities in the Drupal 7 System, and used for internal matters in the Drupal System.  Curious, I took a look at other entries in the table to see what was already being put in there.  Every row had a UUID assigned to it.  

Here's an example of a UUID I found for a user created within the Drupal 7 System:

f4539a1f-9fbb-452c-88fd-198442815002

As it turns out, UUID have several version(s), so the first step is to figure out what version of UUID this is, so we can provide a valid UUID to the Drupal system


Mass Migrating User Information


After performing my research, I began to feel pretty confident in my ability to mass migrate users from the Drupal 6 System to the Drupal 7 System.  The only worry was that pesky uuid value, which I couldn't find much information about.


The Plan


So with the UUID situation resolved, a plan began to emerge:  Write a small program that would connect to the Drupal 6 System database, read the users table and then output a file of database statements acceptable to the Drupal 7 system database.  

Why a file?  Because the Drupal 6 System database resided on a different physical computer than the Drupal 7 System database, so using an intermediate file seemed like a smart way to facilitate a neat inter-machine transfer of information.  Also, I wanted to be able to examine the output file for errors before trying to read it into the Drupal 7 System database.

Other migration srsuggestions I have seen involve writing a program that connects to both Drupal 6 and Drupal 7 at the same time and performing the migration in one big step, but that seemed like an over-concentration of risk to me.  

Ultimately, it looked to me like a mass migration of user data was eminently possible between the Drupal 6 System and the Drupal 7 System - just as long as I was willing to engage in a little bit of data gymnastics.  So here's the general approach I decided to use, which helped me to design the PHP program I used to make the migration happen:

Scenario:

A)  Connect to the Drupal 6 database
B)  Use SQL statements to pull rows from the users table, one at a time
C)  Keep the data needed from each row, and ignore the stuff I didn't need
D)  Write an SQL output file that preserved the maximum amount of user information
E)  Use MySQL on the target machine to import the Drupal 6 users into Drupal 7


Here's a sample from Export-Drupal-6-Users.php, the file I wrote to do that:





In the end, Export-Drupal-6-Users.php functioned quite well.  I was able to preserve the maximum amount of user data from the Drupal 6 System users table.  The data was output to a file in a format that was acceptable to the the Drupal 7 System database engine.  Any missing data was generated and included so the format of the output file conformed to the requirements of the Drupal 7 System database.  Neither system was disrupted as the new data was introduced.  Once the process was complete, I disabled the new user function in the Drupal 6 System, directing new users to the Drupal 7 System instead.  I also disabled the shopping cart functionality, redirecting users to the Drupal 7 System.

Here's a sample of the output of Export-Drupal-6-Users.php, which (as I mentioned) does a bunch of housekeeping on the Drupal 6 System users table as they are processed and re-formatted for the differently-formatted Drupal 7 System users table:



How to Perform a Mass Password Reset

So, with the users migrated, the final step was to get them to reset their password.  This is because passwords cannot be migrated from Drupal 6 to Drupal 7 due to the due to the MD5 -> SHA512 password strategy evolution between the two versions.  I needed to somehow figure out how to do a Selective Mass Password Reset, ideally one that sends all affected users an email with an activation URL that:

A) Re-verifies their current email address
B) Forces forces them to choose a new (and hopefully better) password

Looking around, I found something called the One Time Login Module, which fits those requirements perfectly.  


Done!

Related Articles


How To Migrating Order History From Drupal 6 to Drupal 7 

How To Migrating Addresses from Drupal 6 to Drupal 7


References


https://api.drupal.org/api/drupal/modules%21user%21user.install/function/user_schema/7.x

https://api.drupal.org/api/drupal/modules%21user%21user.install/function/user_schema/6.x




https://smbjorklund.no/how-migrate-content-drupal-6-7-using-migrated2d-part-1

https://justmagicdesign.com/blog/201404/upgrading-website-drupal-6-drupal-7-migrating-users


https://www.drupal.org/forum/support/post-installation/2007-01-06/manually-adding-users

No comments:

Post a Comment