dev/core#2232 - Upgrade UI contaminates cache via l10n-js. Consolidate isUpgradeMode().
authorTim Otten <totten@civicrm.org>
Sat, 12 Dec 2020 03:33:23 +0000 (19:33 -0800)
committerTim Otten <totten@civicrm.org>
Sat, 12 Dec 2020 05:08:59 +0000 (21:08 -0800)
This patch fixes an upgrade bug where `CachedCiviContainer` has stale data after an upgrade.

To do this, it removes an edge-case where two overlapping functions disagree.

Steps to Reproduce
------------------

1. Create a new/empty WP build
2. Install CiviCRM 5.30.1 from zipball
3. Install Mosaico 2.5
4. Download+extract new 5.32.1 zipball
5. Navigate to the GUI upgrade screen. Execute upgrade
6. Click to go back to the CiviCRM dashboard.
7. Receive error about the service 'mosaico_graphics'

Discussion
----------

The problem is created while running the upgrade GUI (step 5).  The GUI sends several HTTP
requests (`civicrm/upgrade`, `civicrm/upgrade/queue/ajax/runNext`, etc).  At the end, there is
an HTTP request for `civicrm/ajax/l10n-js/en_US`.  The `l10n-js` request creates a flawed copy
of `CachedCiviContainer` which is responsible for subsequent errors.

This shouldn't happen - there are defensive mechanism which prevent it from happening (e.g.
during `civicrm/upgrade`).  What makes `civicrm/ajax/l10n-js/en_US` different?  It turns on
the implementation of `isUpgradeMode()` which activates the defensive mechanisms.

Before
------

There are two implementations of `isUpgradeMode()` (via `CRM_Core_Config` and
`CRM_Utils_System`).  They often agree, but not always.

Some defensive measures trigger on `CRM_Core_Config::isUpgradeMode()` and others trigger on
`CRM_Utils_System::isUpgradeMode()`.  They often trigger together, but not always.

Let's see how these can playout in a few HTTP requests:

1. `civicrm/dashboard`: The functions agree -- it's a regular page, not an upgrade.  Therefore:
     * (a) It allows extensions to run fully.
     * (b) It enables `CachedCiviContainer` (read or write automatically).
     * (a+b) It puts good/full information in the cache.
2. `civicrm/upgrade`: The functions agree -- it's an upgrade. Therefore:
     * (a) It runs in paranoid mode (suspended extensions).
     * (b) It disables `CachedCiviContainer`.
     * (a+b) It puts no information in the cache.
3. `civicrm/ajax/l10n-js/en_US`: The functions *disagree*. In this case:
     * (a) It runs in paranoid mode (suspended extensions).
     * (b) It enables `CachedCiviContainer` (read or write automatically).
     * (a+b) It puts bad/incomplete information in the cache.

After
-----

There is one implementation of `isUpgradeMode()`.  It may be called through
either class (`CRM_Core_Config` or `CRM_Utils_System`), but the results will
always agree.

This produces the same appropriate outcome for cases of agreement (1) (2), and it fixes the
wonky/mismatched behavior in (3).

CRM/Core/Config.php
CRM/Utils/System.php

index 1785fa9a93ad020da9ca3443db1328e031cab589..62fa43975502566e0af264df8de518f6e4683aab 100644 (file)
@@ -409,6 +409,11 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
       return TRUE;
     }
 
+    $upgradeInProcess = CRM_Core_Session::singleton()->get('isUpgradePending');
+    if ($upgradeInProcess) {
+      return TRUE;
+    }
+
     if (!$path) {
       // note: do not re-initialize config here, since this function is part of
       // config initialization itself
index 47e72900f0db4c666f233fdc7d20b04496f87373..48abe7d3930a9edff66418da9aa70005ce4e4ad3 100644 (file)
@@ -1846,16 +1846,11 @@ class CRM_Utils_System {
    * Is in upgrade mode.
    *
    * @return bool
+   * @deprecated
+   * @see CRM_Core_Config::isUpgradeMode()
    */
   public static function isInUpgradeMode() {
-    $args = explode('/', CRM_Utils_Array::value('q', $_GET));
-    $upgradeInProcess = CRM_Core_Session::singleton()->get('isUpgradePending');
-    if ((isset($args[1]) && $args[1] == 'upgrade') || $upgradeInProcess) {
-      return TRUE;
-    }
-    else {
-      return FALSE;
-    }
+    return CRM_Core_Config::isUpgradeMode();
   }
 
   /**