CRM-16373 - Civi\Core\SettingsManager - Load defaults *after* core bootstrap.
authorTim Otten <totten@civicrm.org>
Thu, 10 Sep 2015 23:36:28 +0000 (16:36 -0700)
committerTim Otten <totten@civicrm.org>
Thu, 17 Sep 2015 22:49:29 +0000 (15:49 -0700)
CRM/Core/Config.php
Civi/Core/SettingsBag.php
Civi/Core/SettingsManager.php
tests/phpunit/Civi/Core/SettingsManagerTest.php

index ec10378a2900a50e2eb21b6bf6a127bb371ebd27..664bf345ee40ff4695e073c0ede2f62354c590aa 100644 (file)
@@ -101,6 +101,7 @@ class CRM_Core_Config extends CRM_Core_Config_Variables {
 
       CRM_Utils_Hook::config(self::$_singleton);
       self::$_singleton->authenticate();
+      Civi::service('settings_manager')->useDefaults();
     }
     return self::$_singleton;
   }
index 0496cfcf3f3322ed918ff1393c3deb5430fac3d0..6d8810bbf6767eed4da5c4793f22d6d32e76dab0 100644 (file)
@@ -87,28 +87,52 @@ class SettingsBag {
    *   The domain for which we want settings.
    * @param int|NULL $contactId
    *   The contact for which we want settings. Use NULL for domain settings.
+   */
+  public function __construct($domainId, $contactId) {
+    $this->domainId = $domainId;
+    $this->contactId = $contactId;
+    $this->filteredValues = array();
+    $this->combined = NULL;
+  }
+
+  /**
+   * Set/replace the default values.
+   *
    * @param array $defaults
    *   Array(string $settingName => mixed $value).
+   * @return $this
+   */
+  public function loadDefaults($defaults) {
+    $this->defaults = $defaults;
+    $this->filteredValues = array();
+    $this->combined = NULL;
+    return $this;
+  }
+
+  /**
+   * Set/replace the mandatory values.
+   *
    * @param array $mandatory
    *   Array(string $settingName => mixed $value).
+   * @return $this
    */
-  public function __construct($domainId, $contactId, $defaults, $mandatory) {
-    $this->domainId = $domainId;
-    $this->contactId = $contactId;
-    $this->defaults = $defaults;
+  public function loadMandatory($mandatory) {
     $this->mandatory = $mandatory;
+    $this->filteredValues = array();
     $this->combined = NULL;
+    return $this;
   }
 
   /**
-   * Load all settings that apply to this domain or contact.
+   * Load all explicit settings that apply to this domain or contact.
    *
    * @return $this
    */
-  public function load() {
+  public function loadValues() {
     $this->values = array();
-    $dao = $this->createDao();
-    $dao->find();
+    // Note: Don't use Setting DAO. It requires fields() which requires
+    // translations -- which are keyed off settings!
+    $dao = \CRM_Core_DAO::executeQuery($this->createQuery()->toSQL());
     while ($dao->fetch()) {
       $this->values[$dao->name] = ($dao->value !== NULL) ? unserialize($dao->value) : NULL;
     }
@@ -267,19 +291,24 @@ class SettingsBag {
   }
 
   /**
-   * @return \CRM_Core_DAO_Setting
+   * @return \CRM_Utils_SQL_Select
    */
-  protected function createDao() {
-    $dao = new \CRM_Core_DAO_Setting();
-    $dao->domain_id = $this->domainId;
+  protected function createQuery() {
+    $select = \CRM_Utils_SQL_Select::from('civicrm_setting')
+      ->select('id, group_name, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id')
+      ->where('domain_id = #id', array(
+        'id' => $this->domainId,
+      ));
     if ($this->contactId === NULL) {
-      $dao->is_domain = 1;
+      $select->where('is_domain = 1');
     }
     else {
-      $dao->contact_id = $this->contactId;
-      $dao->is_domain = 0;
+      $select->where('contact_id = #id', array(
+        'id' => $this->contactId,
+      ));
+      $select->where('is_domain = 0');
     }
-    return $dao;
+    return $select;
   }
 
   /**
index b6c403e8a43573541bf22542b79eb5b4c11e6706..6f4930dd343ef787b750a2045cba0e06a18b9358 100644 (file)
@@ -31,6 +31,26 @@ namespace Civi\Core;
  * Class SettingsManager
  * @package Civi\Core
  *
+ * The SettingsManager is responsible for tracking settings across various
+ * domains and users.
+ *
+ * Generally, for any given setting, there are three levels where values
+ * can be declared:
+ *
+ *   - Mandatory values (which come from a global $civicrm_setting).
+ *   - Explicit values (which are chosen by the user and stored in the DB).
+ *   - Default values (which come from the settings metadata).
+ *
+ * Note: During the early stages of bootstrap, default values are not be available.
+ * Loading the defaults requires loading metadata from various sources. However,
+ * near the end of bootstrap, one calls SettingsManager::useDefaults() to fetch
+ * and merge the defaults.
+ *
+ * Note: In a typical usage, there will only be one active domain and one
+ * active contact (each having its own bag) within a given request. However,
+ * in some edge-cases, you may need to work with multiple domains/contacts
+ * at the same time.
+ *
  * @see SettingsManagerTest
  */
 class SettingsManager {
@@ -48,12 +68,23 @@ class SettingsManager {
 
   /**
    * @var array
+   *   Array(string $entity => array(string $settingName => mixed $value)).
+   *   Ex: $mandatory['domain']['uploadDir'].
    */
   protected $mandatory = NULL;
 
+  /**
+   * Whether to use defaults.
+   *
+   * @var bool
+   */
+  protected $useDefaults = FALSE;
+
   /**
    * @param \CRM_Utils_Cache_Interface $cache
+   *   A semi-durable location to store metadata.
    * @param NULL|array $mandatory
+   *   Ex: $mandatory['domain']['uploadDir'].
    */
   public function __construct($cache, $mandatory = NULL) {
     $this->cache = $cache;
@@ -61,7 +92,33 @@ class SettingsManager {
   }
 
   /**
-   * @param int $domainId
+   * Ensure that all defaults and mandatory values are included with
+   * all current and future bags.
+   *
+   * @return $this
+   */
+  public function useDefaults() {
+    if (!$this->useDefaults) {
+      $this->useDefaults = TRUE;
+
+      if (!empty($this->bagsByDomain)) {
+        foreach ($this->bagsByDomain as $bag) {
+          $bag->loadDefaults($this->getDefaults('domain'));
+        }
+      }
+
+      if (!empty($this->bagsByContact)) {
+        foreach ($this->bagsByContact as $bag) {
+          $bag->loadDefaults($this->getDefaults('contact'));
+        }
+      }
+    }
+
+    return $this;
+  }
+
+  /**
+   * @param int|NULL $domainId
    * @return SettingsBag
    */
   public function getBagByDomain($domainId) {
@@ -70,18 +127,17 @@ class SettingsManager {
     }
 
     if (!isset($this->bagsByDomain[$domainId])) {
-      $defaults = $this->getDefaults('domain');
-      // Filter $mandatory to only include domain-settings.
-      $mandatory = \CRM_Utils_Array::subset($this->getMandatory(), array_keys($defaults));
-      $this->bagsByDomain[$domainId] = new SettingsBag($domainId, NULL, $defaults, $mandatory);
-      $this->bagsByDomain[$domainId]->load();
+      $this->bagsByDomain[$domainId] = new SettingsBag($domainId, NULL);
+      $this->bagsByDomain[$domainId]->loadValues()
+        ->loadMandatory($this->getMandatory('domain'))
+        ->loadDefaults($this->getDefaults('domain'));
     }
     return $this->bagsByDomain[$domainId];
   }
 
   /**
-   * @param int $domainId
-   * @param int $contactId
+   * @param int|NULL $domainId
+   * @param int|NULL $contactId
    * @return SettingsBag
    */
   public function getBagByContact($domainId, $contactId) {
@@ -91,11 +147,10 @@ class SettingsManager {
 
     $key = "$domainId:$contactId";
     if (!isset($this->bagsByContact[$key])) {
-      $defaults = $this->getDefaults('contact');
-      // Filter $mandatory to only include domain-settings.
-      $mandatory = \CRM_Utils_Array::subset($this->getMandatory(), array_keys($defaults));
-      $this->bagsByContact[$key] = new SettingsBag($domainId, $contactId, $defaults, $mandatory);
-      $this->bagsByContact[$key]->load();
+      $this->bagsByContact[$key] = new SettingsBag($domainId, $contactId);
+      $this->bagsByContact[$key]->loadValues()
+        ->loadDefaults($this->getDefaults('contact'))
+        ->loadMandatory($this->getMandatory('contact'));
     }
     return $this->bagsByContact[$key];
   }
@@ -108,7 +163,11 @@ class SettingsManager {
    * @return array
    *   Array(string $settingName => mixed $value).
    */
-  public function getDefaults($entity) {
+  protected function getDefaults($entity) {
+    if (!$this->useDefaults) {
+      return array();
+    }
+
     $cacheKey = 'defaults:' . $entity;
     $defaults = $this->cache->get($cacheKey);
     if (!is_array($defaults)) {
@@ -127,10 +186,13 @@ class SettingsManager {
   /**
    * Get a list of mandatory/overriden settings.
    *
+   * @param string $entity
+   *   Ex: 'domain' or 'contact'.
    * @return array
    *   Array(string $settingName => mixed $value).
    */
-  public function getMandatory() {
+  protected function getMandatory($entity) {
+    // Prepare and cache list of all mandatory settings.
     if ($this->mandatory === NULL) {
       if (isset($GLOBALS['civicrm_setting'])) {
         $this->mandatory = self::parseMandatorySettings($GLOBALS['civicrm_setting']);
@@ -139,11 +201,22 @@ class SettingsManager {
         $this->mandatory = array();
       }
     }
-    return $this->mandatory;
+
+    return \CRM_Utils_Array::value($entity, $this->mandatory, array());
   }
 
   /**
-   * Parse
+   * Parse mandatory settings.
+   *
+   * In previous versions, settings were broken down into verbose+dynamic group names, e.g.
+   *
+   *   $civicrm_settings['Foo Bar Preferences']['foo'] = 'bar';
+   *
+   * We now simplify to two simple groups, 'domain' and 'contact'.
+   *
+   *    $civicrm_settings['domain']['foo'] = 'bar';
+   *
+   * However, the old groups are grand-fathered in as aliases.
    *
    * @param array $civicrm_setting
    *   Ex: $civicrm_setting['Group Name']['field'] = 'value'.
@@ -151,18 +224,36 @@ class SettingsManager {
    * @return array
    */
   public static function parseMandatorySettings($civicrm_setting) {
-    $tmp = array();
+    $rewriteGroups = array(
+      \CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::EVENT_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME => 'contact',
+      \CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => 'domain',
+      \CRM_Core_BAO_Setting::URL_PREFERENCES_NAME => 'domain',
+    );
+
     if (is_array($civicrm_setting)) {
-      foreach ($civicrm_setting as $group => $settings) {
-        foreach ($settings as $k => $v) {
-          if ($v !== NULL) {
-            $tmp[$k] = $v;
-          }
+      foreach ($rewriteGroups as $oldGroup => $newGroup) {
+        if (!isset($civicrm_setting[$newGroup])) {
+          $civicrm_setting[$newGroup] = array();
+        }
+        if (isset($civicrm_setting[$oldGroup])) {
+          $civicrm_setting[$newGroup] = array_merge($civicrm_setting[$oldGroup], $civicrm_setting[$newGroup]);
+          unset($civicrm_setting[$oldGroup]);
         }
       }
-      return $tmp;
     }
-    return $tmp;
+    return $civicrm_setting;
   }
 
 }
index a3b09f27abb5e79645428a6ee44ee201b5ce78ea..dfeec81b09db2eb72124363df018f8a2744bb4c6 100644 (file)
@@ -28,10 +28,10 @@ class SettingsManagerTest extends \CiviUnitTestCase {
       'c3' => 'gamma',
     );
     $this->mandates = array(
-      'foo' => array(
+      'Mailing Preferences' => array(
         'd3' => 'GAMMA!',
       ),
-      'bar' => array(
+      'contact' => array(
         'c3' => 'GAMMA MAN!',
       ),
     );
@@ -44,7 +44,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $da = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
     $db = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
 
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
 
     $daSettings = $manager->getBagByDomain($da->id);
     $daSettings->set('d1', 'un');
@@ -57,7 +57,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $this->assertEquals('beta', $dbSettings->get('d2'));
     $this->assertEquals('GAMMA!', $dbSettings->get('d3'));
 
-    $managerRedux = $this->createManager();
+    $managerRedux = $this->createManager()->useDefaults();
 
     $daSettingsRedux = $managerRedux->getBagByDomain($da->id);
     $this->assertEquals('un', $daSettingsRedux->get('d1'));
@@ -73,7 +73,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $ca = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
     $cb = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
 
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
 
     $caSettings = $manager->getBagByContact($domain->id, $ca->id);
     $caSettings->set('c1', 'un');
@@ -87,7 +87,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $this->assertEquals('GAMMA MAN!', $cbSettings->get('c3'));
 
     // Read settings from freshly initialized objects.
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
 
     $caSettingsRedux = $manager->getBagByContact($domain->id, $ca->id);
     $this->assertEquals('un', $caSettingsRedux->get('c1'));
@@ -99,7 +99,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
     $contact = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
 
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
 
     // Store different values for the 'monkeywrench' setting on domain and contact
 
@@ -112,7 +112,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
     $this->assertEquals('from contact', $contactSettings->get('monkeywrench'));
 
     // Read settings from freshly initialized objects.
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
 
     $domainSettings = $manager->getBagByDomain($domain->id);
     $this->assertEquals('from domain', $domainSettings->get('monkeywrench'));
@@ -123,7 +123,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
 
   public function testPaths() {
     $domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
     $settings = $manager->getBagByDomain($domain->id);
 
     $this->assertEquals('foo', $settings->get('myrelpath'));
@@ -140,7 +140,7 @@ class SettingsManagerTest extends \CiviUnitTestCase {
 
   public function testUrl() {
     $domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
-    $manager = $this->createManager();
+    $manager = $this->createManager()->useDefaults();
     $settings = $manager->getBagByDomain($domain->id);
 
     $this->assertEquals('sites/foo', $settings->get('myrelurl'));