Showing posts with label Ubercart. Show all posts
Showing posts with label Ubercart. Show all posts

Thursday, November 14, 2019

Drupal 7 / Ubercart 7 - Migrating Drupal 6 Ubercart Addresses to Drupal 7 Ubercart

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



One of the things I want to avoid (because I hate it when it happens to me!) is when a website upgrades, they lose all of my data and I have to re-enter it all over again.  It's a small thing, I know - but why should I have to re-enter my information when it was them who decided to upgrade?  It seems unfair somehow to offload the data entry to the customer, when it is the vendor making the decision to make a change.

Migrating Drupal 6 Ubercart Addresses to Drupal 7 Ubercart


Now that all of our active customers have been migrated from the Drupal 6 System to the Drupal 7 System, we want to continue to make their inter-system transition as easy as possible.

Let's see what we are up against.

First, we have to figure out where customer addresses are stored in the Drupal 6 System.

Well, it looks like we may be in luck.  There are two tables in our Drupal 6 System with very evocative names:

uc_addresses

uc_addresses_defaults

Let's see what these tables are all about, shall we?

In the Drupal 6 System, the uc_addresses table has the following schema:

mysql> show columns from uc_addresses
+--------------+-----------------------+------+-----+---------+----------------+
| Field        | Type                  | Null | Key | Default | Extra          |
+--------------+-----------------------+------+-----+---------+----------------+
| aid          | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| uid          | int(10) unsigned      | NO   |     | 0       |                |
| first_name   | varchar(255)          | NO   |     |         |                |
| last_name    | varchar(255)          | NO   |     |         |                |
| phone        | varchar(255)          | NO   |     |         |                |
| company      | varchar(255)          | NO   |     |         |                |
| street1      | varchar(255)          | NO   |     |         |                |
| street2      | varchar(255)          | NO   |     |         |                |
| city         | varchar(255)          | NO   |     |         |                |
| zone         | mediumint(9)          | NO   |     | 0       |                |
| postal_code  | varchar(255)          | NO   |     |         |                |
| country      | mediumint(8) unsigned | NO   |     | 0       |                |
| address_name | varchar(20)           | YES  |     | NULL    |                |
| created      | int(11)               | NO   |     | 0       |                |
| modified     | int(11)               | NO   |     | 0       |                |
+--------------+-----------------------+------+-----+---------+----------------+

So it seems to me that:

1) User addresses are stored in the Drupal 6 System uc_addresses table
2) The address function is part of the Ubercart 2 subsystem
3) Everything is tied to the user uid value that appears in the users table

OK, now for a look at the schema of the uc_addresses_defaults table:

mysql> show columns from uc_addresses_defaults;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| aid   | int(10) unsigned | NO   | PRI | NULL    |       |
| uid   | int(10) unsigned | NO   | PRI | NULL    |       |

+-------+------------------+------+-----+---------+-------+

OK, this is a classic database lookup table that simply "glues" together two other tables (in this case, users, and uc_addresses) together by their primary keys,  uid and aid.

Great!  This is beginning to look really straightforward!

But before celebrating, let's have a look at the Drupal 7 System.

Hmmm...there's only one table!

uc_addresses

What's the table schema look like?

MariaDB [hph]> show columns from uc_addresses;
+------------------+-----------------------+------+-----+---------+----------------+
| Field            | Type                  | Null | Key | Default | Extra          |
+------------------+-----------------------+------+-----+---------+----------------+
| aid              | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| uid              | int(10) unsigned      | NO   |     | 0       |                |
| first_name       | varchar(255)          | NO   |     |         |                |
| last_name        | varchar(255)          | NO   |     |         |                |
| phone            | varchar(255)          | NO   |     |         |                |
| company          | varchar(255)          | NO   |     |         |                |
| street1          | varchar(255)          | NO   |     |         |                |
| street2          | varchar(255)          | NO   |     |         |                |
| city             | varchar(255)          | NO   |     |         |                |
| zone             | mediumint(9)          | NO   |     | 0       |                |
| postal_code      | varchar(255)          | NO   |     |         |                |
| country          | mediumint(8) unsigned | NO   |     | 0       |                |
| address_name     | varchar(20)           | YES  |     | NULL    |                |
| default_shipping | tinyint(4)            | NO   |     | NULL    |                |
| default_billing  | tinyint(4)            | NO   |     | NULL    |                |
| created          | int(11)               | NO   |     | 0       |                |
| modified         | int(11)               | NO   |     | 0       |                |
+------------------+-----------------------+------+-----+---------+----------------+

No the same.

Worse yet, there's no uc_addreses_defaults table in the Drupal 7 System!

Maybe there's a module installed in the Drupal 6 System that isn't present in the Drupal 7 System that has something to do with "Multiple", "Preferred" or "Default" addresses....Nope.

So what the Ubercart people probably did was to collapse the functionality of those two tables into one, probably because it turned out that people often have multiple billing and shipping addresses, and sometimes they want to use the default billing address and/or default shipping address, and sometimes they don't.

So what the Drupal 7 people chose to do was to use a tinyint(4) flag instead to "tag" the record type with a 0 or a 1 to indicate if it is the default_billing or default_shipping (or both).  This imposes a little bit of overhead on each address record,  but it also saves a disk access because everthing is captured in one place.  

Database designers can go either way on these design decisions, in Drupal 6 they decided to go 3NF, in Drupal 7 they decided to go 2NF.  Both have their pros and cons.  This only becomes a drag when you move from one design strategy to another, like they did - because it makes moving data complicated.

Here's a side-by-side comparison of the schemas of the two tables:



Yes, the only difference between the two tables is the default_shipping and default_billing flags.


Plan of Action:


OK, a plan of action is starting to emerge.  Just moving the data over wholesale like we did when we moved the users is a non-starter, because we have since purged a few hundred users from our Drupal 6 System database, and their addresses might be bloating the uc_addresses table(s).  

Let's get some numbers to check on that concern:

mysql> select count(*) from uc_addresses;
+----------+
| count(*) |
+----------+
|     1338 |

+----------+

OK, so it looks like we have 1338 addresses...but how many unique users do we have?

mysql> select count(*) from users;

+----------+

| count(*) |

+----------+

|      885 |

+----------+


Hmmm...only 885 unique users.

So, let's do a quick look for "orphan" address records, which would happen when the uid in the uc_addresses table does not exist as a uid in the users table;

mysql> select distinct(uid) from users order by uid;
<output removed for brevity>
885 rows in set (0.00 sec)


mysql> select distinct(uid) from uc_addresses order by uid;
<output removed for brevity>
882 rows in set (0.00 sec)

Alright, we have 885 unique entries in the users table, including user 0 (Anonymous) and user 1 (Administrator).  Looks like we have a user that has NO address.  Let's go find them!

mysql> select distinct(uid) from users order by uid into outfile '~/users-uid.txt';

mysql> select distinct(uid) from uc_addresses order by uid into outfile '~/addresses-uid.txt';

Then we downloaded the resulting files (user-uid.txt and address-uid.txt) to our local system and then imported them into MS-EXCEL.  Once they were in MS-EXCEL, we did a little programming to come up with the following result:




Every other row matched, so we really only have to pay attention to four (4) users of interest.

The first two are internal Drupal accounts:
User
0 (Anonymous) shouldn't have an entry in the uc_addresses table, and it doesn't.  Good.  

User 1 (Administrator) could have an entry in the uc_addresses table, and it does.  Good

The final two rows are external, customer accounts, and they should have an entry in the uc_addresses table.  The fact that they didn't is well worth looking into, because that may mean that thay registered, but never bought anything from us:

User 2340 
User 2357 

As it turns out, the following users have always ordered by telephone and never online, so their address was never entered into the Drupal 6 System database.  But it they were in our accounting system, so we just entered their address data in for them.  

Once that was done, every non-internal account had an address associated with it.

Now that we have balanced tables, things are getting easier and easier.



OK, now every user has at least one address entered into the system, but some users also have multiple addresses entered into the sytem, so we are going to need to figure out which address is their preferred (or default) Shipping and Billing address.

This is where the uc_default_addresses table now comes into play.

Looking into the table, it turned out that every uid that should have had a default address did, even the Administrator user.




So things are looking fairly simple now.  Here's the plan:

1) On System A, connect to the Drupal 6 System database
2) Read the uc_addresses table, row by row
3) Check each row to see if it appears in the uc_addresses_defaults table
4) If it does appear in the uc_addresses_defaults table, flag it as the default Billing and Shipping address
5) If it does not appear in the uc_addresses_defaults table, enter it as an unflagged, alternative address
6) Once finished, move the data file physically from System A to System B
7) On System B, connect to the Drupal 7 System database
8) Read the data file directly into the Drupal 7 System database

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


In the end, Export-Drupal-6-Ubercart-Addresses.php functioned quite well.  


  • I was able to preserve the maximum amount of address data from the Drupal 6 Ubercart uc_addresses and uc_addresse_defaults tables.  
  • Any missing data was generated and included so the format of the output file conformed to the requirements of the Drupal 7 System database.
  • The data was output to a file in a format that was acceptable to the the Drupal 7 System database engine.
  • Neither system was disrupted as the new data was introduced.
  

REFERENCES:




Sunday, November 3, 2019

Drupal 7 / Ubercart 7 - Resolving a Media Module Update FAILURE

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

 

Resolving a Media Module Update FAILURE

 
So, one day, I got this email from the Drupal system:



The body of the email looked like this:



So, I went to the website to run cron to get an overall report on the status of the Drupal 7 system:



Here's what I got as a result:



So, I clicked on available updates to see what needed updating



OK, the Media Module needs updating.  Fine.

So, I checked Media and clicked on the Download these updates button:



Not having much to say about the presented information, I clicked on Continue:

DRUPAL:  Failure(s) on Multiple Levels

First of all, the error message is truncated, making the error condition hard to diagnose.

Second, there's clearly a file permissions problem or a filesystem security problem (or both) at play here, with no diagnostic messaging in the offing to help anyone figure out what the nature of the error might be.

Third, why are Next steps being displayed when the system is clearly in an error condition?


Fourth, my site is now in maintenance mode, which means customers cannot access my site while I am figuring out yet another obscure Drupal error. 

Showing the Entire Error:

I suppose seeing the entire error is too much to ask of the Drupal 7 System?

Thankfully, I know a trick I learned ten years ago when I first started struggling with Drupal - which is to use CTRL-A to highlight the entire screen and then paste the content into Microsoft Notepad.  

Here's what I got:



So now we at least know where the error is and what it pertains to:

File Transfer failed

Reason: Cannot remove file <root>/sites/all/modules/media/README.txt

So there's some problem with the apache server being able to manipulate files in the area of the Drupal 7 system related to /modules, and this error manifested when the Apache user tried to mess with the README.txt file.

Hmmm...these kinds of errors were already addressed in an earlier article I wrote about Enabling the Drupal 7 GUI to Upload Modules:


https://mymanthemaker.blogspot.com/2019/10/enabling-d7-to-upload-modules.html

So let's quickly scan that article and use that information to help guide us while we take a look at the permissions of the modules directory, shall we?


drwxr-xr-x. 54 apache apache  4096 Oct 24 12:11 modules

The /modules directory looks cool from an ownership (apache:apache) and file permissions (755) perspective, but what about its SELINUX mode?

unconfined_u:object_r:httpd_sys_rw_content_t:s0 modules


Well, this SELINUX security context is also cool (rw)

Let's drill down to the next layer, shall we?

# cd modules
# ls -l

drwxr-xr-x. 10 root  root  4096 Jul 15 23:31 media

This may be the problem.  Wrong owner (root:root).  So, I must have installed this module manually at some point and not set the ownership properly once I was done, because I don't get these kinds of errors when I use the command line as opposed to the GUI.  

This is because when I am at the command line my security context is root, an account with the power to do anything.  But when I am using the GUI I am considered by Linux to be the apache user (which is the web servers security context) which has a much more limited set of rights than does root.

Let's fix the ownership issue, and see what happens:

# chown apache:apache ./media -R

OK, let's re-run the update.  But first we need to find where in the Administration Interface to go to make an update happen, because the Drupal 7 Module Update error screen offers nowhere to go.







UTTER FAILURE AGAIN

OK, there's one more thing to check - the SELINUX permissions:

# ls -lZ 

unconfined_u:object_r:httpd_sys_content_t:s0 media

Well, that's no good, it needs to be read/write (rw) for Apache to be able to make changes:

chcon unconfined_u:object_r:httpd_sys_rw_content_t:s0 ./media -R

Now, let's try that update again.


OK, click on Continue for the third time now...


Alright!  Finally!  Success!

Now we can click on Run database updates to finalize this fix.


Click on Continue:


Looks like we are done.

Drupal 7 / Ubercart 7 - Stability Theme Change Default Catalog Sort Order

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 with this problem, or any other form of Drupal Wizardry, feel free to contact us via our new division, Drupal Wizard, which 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.

Stability Theme Change Default Catalog Sort Order


Currently, the sort order of the catalog in the Stability Theme is by reverse chronological
order, meaning that the most recent items appear at the top.  This makes sense sometimes, but it is a fairly rudimentary way of displaying products.

Some other potential ways of ordering a catalog are:


  • Alphabetic
  • Weight
  • Random
  • Sales
  • Score
  • Popularity / Recently Purchased

At the moment, we prefer to list our products Alphabetically, because our customers find a product line they like, and then buy products within that line.  Controlling the sort order gives us complete control over how our catalog is presented to our customers.

All of this happens in a View, which is the Drupal way of executing a database query.  So, the first we need to figure out is which View we need to change:



Because it has /shop in it, this View is a very likely candidate,  so we are going to take a close look at it:


For reference, this is the Products (Content) View:



Looking at the Shop Full Width aspect, we can see the SORT CRITERIA is
  • Post date (desc)

This would display the products in the reverse order they were entered, which is what we are currently getting. but not what we want.

What we want is to have the SORT CRITERA changed to an alphabetic list in ascending order.  This critiera is:


  • Content: Title (asc)



So, here's what we did to get our catalog to present products in the correct sort order:

In the SORT CRITERIA, click on Add:

Select Content: Title



Accept Sort ascending as the sort order



Click on Apply (all displays).

Next, we need to remove the obsolete Post date (desc) sort order criteria:

Click on Post date (desc).

Click on Remove:



Scroll to the bottom of the page to see if the View is producing the correct results.

Yes, looks great!

So, now that the View is functioning right, save it.

Click Save, which is in the upper right corner of the screen:



Voila, problem solved.