Civi::settings() - Add short hand for accessing domain settings
authorTim Otten <totten@civicrm.org>
Wed, 19 Aug 2015 13:27:43 +0000 (06:27 -0700)
committerTim Otten <totten@civicrm.org>
Thu, 17 Sep 2015 22:44:59 +0000 (15:44 -0700)
CRM/Core/BAO/Setting.php
Civi.php
Civi/Core/Container.php
Civi/Core/SettingsBag.php [new file with mode: 0644]
Civi/Core/SettingsManager.php [new file with mode: 0644]
tests/phpunit/Civi/Core/SettingsManagerTest.php [new file with mode: 0644]
tests/phpunit/api/v3/SettingTest.php

index 4ef45dd27b34ef304b9b91e79629135b1173415a..cf2d834efb2f33c4417567d6feb18f84735447c5 100644 (file)
@@ -224,61 +224,21 @@ class CRM_Core_BAO_Setting extends CRM_Core_DAO_Setting {
     $contactID = NULL,
     $domainID = NULL
   ) {
-
-    $overrideGroup = array();
-    if (NULL !== ($override = self::getOverride($group, $name, NULL))) {
-      if (isset($name)) {
-        return $override;
-      }
-      else {
-        $overrideGroup = $override;
-      }
-    }
-
-    if (empty($domainID)) {
-      $domainID = CRM_Core_Config::domainID();
-    }
-    $cacheKey = self::inCache($group, $name, $componentID, $contactID, TRUE, $domainID);
-
-    if ($group && !isset($name) && $cacheKey) {
-      // check value against the cache, and unset key if values are different
-      $valueDifference = CRM_Utils_Array::multiArrayDiff($overrideGroup, self::$_cache[$cacheKey]);
-      if (!empty($valueDifference)) {
-        $cacheKey = '';
+    /** @var \Civi\Core\SettingsManager $manager */
+    $manager = \Civi::service('settings_manager');
+    $settings = ($contactID === NULL) ? $manager->getBagByDomain($domainID) : $manager->getBagByContact($domainID, $contactID);
+    if (TRUE) {
+      if ($name === NULL) {
+        CRM_Core_Error::debug_log_message("Deprecated: Group='$group'. Name should be provided.\n");
       }
-    }
-
-    if (!$cacheKey) {
-      $dao = self::dao($group, NULL, $componentID, $contactID, $domainID);
-      $dao->find();
-
-      $values = array();
-      while ($dao->fetch()) {
-        if (NULL !== ($override = self::getOverride($group, $dao->name, NULL))) {
-          $values[$dao->name] = $override;
-        }
-        elseif ($dao->value) {
-          $values[$dao->name] = unserialize($dao->value);
-        }
-        else {
-          $values[$dao->name] = NULL;
-        }
+      if ($componentID !== NULL) {
+        CRM_Core_Error::debug_log_message("Deprecated: Group='$group'. Name='$name'. Component should be omitted\n");
       }
-      $dao->free();
-
-      if (!isset($name)) {
-        // merge db and override group values
-        // When no $name is present, the getItem() function should return an array
-        // consisting of the sum of all override settings + all settings present in
-        // the database for the given $group (with the overrides taking precedence,
-        // and applying even if the setting is not defined in the database).
-        //
-        $values = array_merge($values, $overrideGroup);
+      if ($defaultValue !== NULL) {
+        CRM_Core_Error::debug_log_message("Deprecated: Group='$group'. Name='$name'. Defaults should come from metadata\n");
       }
-
-      $cacheKey = self::setCache($values, $group, $componentID, $contactID, $domainID);
     }
-    return $name ? CRM_Utils_Array::value($name, self::$_cache[$cacheKey], $defaultValue) : self::$_cache[$cacheKey];
+    return $name ? $settings->get($name) : $settings->all();
   }
 
   /**
@@ -373,14 +333,10 @@ class CRM_Core_BAO_Setting extends CRM_Core_DAO_Setting {
     $createdID = NULL,
     $domainID = NULL
   ) {
-    $fields = array();
-    $fieldsToSet = self::validateSettingsInput(array($name => $value), $fields);
-    //We haven't traditionally validated inputs to setItem, so this breaks things.
-    //foreach ($fieldsToSet as $settingField => &$settingValue) {
-    //  self::validateSetting($settingValue, $fields['values'][$settingField]);
-    //}
-
-    return self::_setItem($fields['values'][$name], $value, $group, $name, $componentID, $contactID, $createdID, $domainID);
+    /** @var \Civi\Core\SettingsManager $manager */
+    $manager = \Civi::service('settings_manager');
+    $settings = ($contactID === NULL) ? $manager->getBagByDomain($domainID) : $manager->getBagByContact($domainID, $contactID);
+    $settings->set($name, $value);
   }
 
   /**
@@ -485,10 +441,13 @@ class CRM_Core_BAO_Setting extends CRM_Core_DAO_Setting {
    * @return array
    */
   public static function setItems(&$params, $domains = NULL) {
-    $originalDomain = CRM_Core_Config::domainID();
-    if (empty($domains)) {
-      $domains[] = $originalDomain;
-    }
+    /** @var \Civi\Core\SettingsManager $manager */
+    $manager = \Civi::service('settings_manager');
+    $domains = empty($domains) ? array(CRM_Core_Config::domainID()) : $domains;
+
+    // FIXME: redundant validation
+    // FIXME: this whole thing should just be a loop to call $settings->add() on each domain.
+
     $reloadConfig = FALSE;
     $fields = $config_keys = array();
     $fieldsToSet = self::validateSettingsInput($params, $fields);
@@ -498,23 +457,16 @@ class CRM_Core_BAO_Setting extends CRM_Core_DAO_Setting {
     }
 
     foreach ($domains as $domainID) {
+
       if ($domainID != CRM_Core_Config::domainID()) {
         $reloadConfig = TRUE;
         CRM_Core_BAO_Domain::setDomain($domainID);
       }
       $result[$domainID] = array();
+      $realSettingsToSet = array(); // need to separate config_backend stuff
       foreach ($fieldsToSet as $name => $value) {
         if (empty($fields['values'][$name]['config_only'])) {
-          CRM_Core_BAO_Setting::_setItem(
-            $fields['values'][$name],
-            $value,
-            $fields['values'][$name]['group_name'],
-            $name,
-            CRM_Utils_Array::value('component_id', $params),
-            CRM_Utils_Array::value('contact_id', $params),
-            CRM_Utils_Array::value('created_id', $params),
-            $domainID
-          );
+          $realSettingsToSet[$name] = $value;
         }
         if (!empty($fields['values'][$name]['prefetch'])) {
           if (!empty($fields['values'][$name]['config_key'])) {
@@ -524,6 +476,7 @@ class CRM_Core_BAO_Setting extends CRM_Core_DAO_Setting {
         }
         $result[$domainID][$name] = $value;
       }
+      $manager->getBagByDomain($domainID)->add($realSettingsToSet);
       if ($reloadConfig) {
         CRM_Core_Config::singleton($reloadConfig, $reloadConfig);
       }
index d4fe840ce686748d75395e73409542f7051c8a47..20d78b670a25e772e5d4ebf10d9fcef720fb9347 100644 (file)
--- a/Civi.php
+++ b/Civi.php
@@ -74,4 +74,15 @@ class Civi {
     self::$statics = array();
   }
 
+  /**
+   * Obtain the domain settings.
+   *
+   * @param int|null $domainID
+   *   For the default domain, leave $domainID as NULL.
+   * @return \Civi\Core\SettingsBag
+   */
+  public static function settings($domainID = NULL) {
+    return Civi\Core\Container::singleton()->get('settings_manager')->getBagByDomain($domainID);
+  }
+
 }
index 7b4b611468955c2955a7841f3667025ab4df4f7e..2be91acca1ce14eaaa6e1fa26a685dca0f281b56 100644 (file)
@@ -103,6 +103,18 @@ class Container {
     ))
       ->setFactoryClass('CRM_Cxn_BAO_Cxn')->setFactoryMethod('createRegistrationClient');
 
+    $container->setDefinition('cache.settings', new Definition(
+      'CRM_Utils_Cache_SqlGroup',
+      array(
+        array('group' => 'Settings', 'prefetch' => 0),
+      )
+    ));
+
+    $container->setDefinition('settings_manager', new Definition(
+      'Civi\Core\SettingsManager',
+      array(new Reference('cache.settings'))
+    ));
+
     $container->setDefinition('pear_mail', new Definition('Mail'))
       ->setFactoryClass('CRM_Utils_Mail')->setFactoryMethod('createMailer');
 
diff --git a/Civi/Core/SettingsBag.php b/Civi/Core/SettingsBag.php
new file mode 100644 (file)
index 0000000..55a696c
--- /dev/null
@@ -0,0 +1,270 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Core;
+
+/**
+ * Class SettingsBag
+ * @package Civi\Core
+ *
+ * Read and write settings for a given domain (or contact).
+ *
+ * If the target entity does not already have a value for the setting, then
+ * the defaults will be used. If mandatory values are provided, they will
+ * override any defaults or custom settings.
+ *
+ * It's expected that the SettingsBag will have O(50-250) settings -- and that
+ * we'll load the full bag on many page requests. Consequently, we don't
+ * want the full metadata (help text and version history and HTML widgets)
+ * for all 250 settings, but we do need the default values.
+ *
+ * This class is not usually instantiated directly. Instead, use SettingsManager
+ * or Civi::settings().
+ *
+ * @see \Civi::settings()
+ * @see SettingsManagerTest
+ */
+class SettingsBag {
+
+  protected $domainId;
+
+  protected $contactId;
+
+  /**
+   * @var array
+   *   Array(string $settingName => mixed $value).
+   */
+  protected $defaults;
+
+  /**
+   * @var array
+   *   Array(string $settingName => mixed $value).
+   */
+  protected $mandatory;
+
+  /**
+   * The result of combining default values, mandatory
+   * values, and user values.
+   *
+   * @var array|NULL
+   *   Array(string $settingName => mixed $value).
+   */
+  protected $combined;
+
+  /**
+   * @var array
+   */
+  protected $values;
+
+  /**
+   * @param int $domainId
+   *   The domain for which we want settings.
+   * @param int|NULL $contactId
+   *   The contact for which we want settings. Use NULL for domain settings.
+   * @param array $defaults
+   *   Array(string $settingName => mixed $value).
+   * @param array $mandatory
+   *   Array(string $settingName => mixed $value).
+   */
+  public function __construct($domainId, $contactId, $defaults, $mandatory) {
+    $this->domainId = $domainId;
+    $this->contactId = $contactId;
+    $this->defaults = $defaults;
+    $this->mandatory = $mandatory;
+    $this->combined = NULL;
+  }
+
+  /**
+   * Load all settings that apply to this domain or contact.
+   *
+   * @return $this
+   */
+  public function load() {
+    $this->values = array();
+    $dao = $this->createDao();
+    $dao->find();
+    while ($dao->fetch()) {
+      $this->values[$dao->name] = is_string($dao->value) ? unserialize($dao->value) : NULL;
+    }
+    $this->combined = NULL;
+    return $this;
+  }
+
+  /**
+   * Add a batch of settings. Save them.
+   *
+   * @param array $settings
+   *   Array(string $settingName => mixed $settingValue).
+   * @return $this
+   */
+  public function add(array $settings) {
+    foreach ($settings as $key => $value) {
+      $this->set($key, $value);
+    }
+    return $this;
+  }
+
+  /**
+   * Get a list of all effective settings.
+   *
+   * @return array
+   *   Array(string $settingName => mixed $settingValue).
+   */
+  public function all() {
+    if ($this->combined === NULL) {
+      $this->combined = $this->combine(
+        array($this->defaults, $this->values, $this->mandatory)
+      );
+    }
+    return $this->combined;
+  }
+
+  /**
+   * Determine the effective value.
+   *
+   * @param string $key
+   * @return mixed
+   */
+  public function get($key) {
+    $all = $this->all();
+    return isset($all[$key]) ? $all[$key] : NULL;
+  }
+
+  /**
+   * Determine the explicitly designated value, regardless of
+   * any default or mandatory values.
+   *
+   * @param string $key
+   * @return null
+   */
+  public function getExplicit($key) {
+    return (isset($this->values[$key]) ? $this->values[$key] : NULL);
+  }
+
+  /**
+   * Determine if the entity has explicitly designated a value.
+   *
+   * Note that get() may still return other values based on
+   * mandatory values or defaults.
+   *
+   * @param string $key
+   * @return bool
+   */
+  public function hasExplict($key) {
+    // NULL means no designated value.
+    return isset($this->values[$key]);
+  }
+
+  /**
+   * Removes any explicit settings. This restores the default.
+   *
+   * @param string $key
+   * @return $this
+   */
+  public function revert($key) {
+    // It might be better to DELETE (to avoid long-term leaks),
+    // but setting NULL is simpler for now.
+    return $this->set($key, NULL);
+  }
+
+  /**
+   * Add a single setting. Save it.
+   *
+   * @param string $key
+   * @param mixed $value
+   * @return $this
+   */
+  public function set($key, $value) {
+    $this->setDb($key, $value);
+    $this->values[$key] = $value;
+    $this->combined = NULL;
+    return $this;
+  }
+
+  /**
+   * @return \CRM_Core_DAO_Setting
+   */
+  protected function createDao() {
+    $dao = new \CRM_Core_DAO_Setting();
+    $dao->domain_id = $this->domainId;
+    if ($this->contactId === NULL) {
+      $dao->is_domain = 1;
+    }
+    else {
+      $dao->contact_id = $this->contactId;
+      $dao->is_domain = 0;
+    }
+    return $dao;
+  }
+
+  /**
+   * Combine a series of arrays, excluding any
+   * null values. Later values override earlier
+   * values.
+   *
+   * @param $arrays
+   * @return array
+   */
+  protected function combine($arrays) {
+    $combined = array();
+    foreach ($arrays as $array) {
+      foreach ($array as $k => $v) {
+        if ($v !== NULL) {
+          $combined[$k] = $v;
+        }
+      }
+    }
+    return $combined;
+  }
+
+  /**
+   * @param $key
+   * @param $value
+   */
+  protected function setDb($name, $value) {
+    $fields = array();
+    $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput(array($name => $value), $fields);
+    //We haven't traditionally validated inputs to setItem, so this breaks things.
+    //foreach ($fieldsToSet as $settingField => &$settingValue) {
+    //  self::validateSetting($settingValue, $fields['values'][$settingField]);
+    //}
+    // NOTE: We don't have any notion of createdID
+    \CRM_Core_BAO_Setting::_setItem($fields['values'][$name], $value, '', $name, NULL, $this->contactId, NULL, $this->domainId);
+
+    //$dao = $this->createDao();
+    //$dao->name = $key;
+    //$dao->group_name = '';
+    //$dao->find();
+    //$serializedValue = ($value === NULL ? 'null' : serialize($value));
+    //if ($dao->value !== $serializedValue) {
+    //  $dao->created_date = \CRM_Utils_Time::getTime('Ymdhis');
+    //  $dao->value = $serializedValue;
+    //  $dao->save();
+    //}
+  }
+
+}
diff --git a/Civi/Core/SettingsManager.php b/Civi/Core/SettingsManager.php
new file mode 100644 (file)
index 0000000..b6c403e
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Core;
+
+/**
+ * Class SettingsManager
+ * @package Civi\Core
+ *
+ * @see SettingsManagerTest
+ */
+class SettingsManager {
+
+  /**
+   * @var \CRM_Utils_Cache_Interface
+   */
+  protected $cache;
+
+  /**
+   * @var
+   *   Array (int $id => SettingsBag $bag).
+   */
+  protected $bagsByDomain = array(), $bagsByContact = array();
+
+  /**
+   * @var array
+   */
+  protected $mandatory = NULL;
+
+  /**
+   * @param \CRM_Utils_Cache_Interface $cache
+   * @param NULL|array $mandatory
+   */
+  public function __construct($cache, $mandatory = NULL) {
+    $this->cache = $cache;
+    $this->mandatory = $mandatory;
+  }
+
+  /**
+   * @param int $domainId
+   * @return SettingsBag
+   */
+  public function getBagByDomain($domainId) {
+    if ($domainId === NULL) {
+      $domainId = \CRM_Core_Config::domainID();
+    }
+
+    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();
+    }
+    return $this->bagsByDomain[$domainId];
+  }
+
+  /**
+   * @param int $domainId
+   * @param int $contactId
+   * @return SettingsBag
+   */
+  public function getBagByContact($domainId, $contactId) {
+    if ($domainId === NULL) {
+      $domainId = \CRM_Core_Config::domainID();
+    }
+
+    $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();
+    }
+    return $this->bagsByContact[$key];
+  }
+
+  /**
+   * Determine the default settings.
+   *
+   * @param string $entity
+   *   Ex: 'domain' or 'contact'.
+   * @return array
+   *   Array(string $settingName => mixed $value).
+   */
+  public function getDefaults($entity) {
+    $cacheKey = 'defaults:' . $entity;
+    $defaults = $this->cache->get($cacheKey);
+    if (!is_array($defaults)) {
+      $specs = \CRM_Core_BAO_Setting::getSettingSpecification(NULL, array(
+        'is_contact' => ($entity === 'contact' ? 1 : 0),
+      ));
+      $defaults = array();
+      foreach ($specs as $key => $spec) {
+        $defaults[$key] = \CRM_Utils_Array::value('default', $spec);
+      }
+      $this->cache->set($cacheKey, $defaults);
+    }
+    return $defaults;
+  }
+
+  /**
+   * Get a list of mandatory/overriden settings.
+   *
+   * @return array
+   *   Array(string $settingName => mixed $value).
+   */
+  public function getMandatory() {
+    if ($this->mandatory === NULL) {
+      if (isset($GLOBALS['civicrm_setting'])) {
+        $this->mandatory = self::parseMandatorySettings($GLOBALS['civicrm_setting']);
+      }
+      else {
+        $this->mandatory = array();
+      }
+    }
+    return $this->mandatory;
+  }
+
+  /**
+   * Parse
+   *
+   * @param array $civicrm_setting
+   *   Ex: $civicrm_setting['Group Name']['field'] = 'value'.
+   *   Group names are an historical quirk; ignore them.
+   * @return array
+   */
+  public static function parseMandatorySettings($civicrm_setting) {
+    $tmp = array();
+    if (is_array($civicrm_setting)) {
+      foreach ($civicrm_setting as $group => $settings) {
+        foreach ($settings as $k => $v) {
+          if ($v !== NULL) {
+            $tmp[$k] = $v;
+          }
+        }
+      }
+      return $tmp;
+    }
+    return $tmp;
+  }
+
+}
diff --git a/tests/phpunit/Civi/Core/SettingsManagerTest.php b/tests/phpunit/Civi/Core/SettingsManagerTest.php
new file mode 100644 (file)
index 0000000..60263f3
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+namespace Civi\Core;
+
+require_once 'CiviTest/CiviUnitTestCase.php';
+
+class SettingsManagerTest extends \CiviUnitTestCase {
+
+  protected $domainDefaults;
+  protected $contactDefaults;
+  protected $mandates;
+
+  protected function setUp() {
+    parent::setUp();
+    $this->useTransaction(TRUE);
+
+    $this->domainDefaults = array(
+      'd1' => 'alpha',
+      'd2' => 'beta',
+      'd3' => 'gamma',
+    );
+    $this->contactDefaults = array(
+      'c1' => 'alpha',
+      'c2' => 'beta',
+      'c3' => 'gamma',
+    );
+    $this->mandates = array(
+      'foo' => array(
+        'd3' => 'GAMMA!',
+      ),
+      'bar' => array(
+        'c3' => 'GAMMA MAN!',
+      ),
+    );
+  }
+
+  /**
+   * Test mingled reads/writes of settings for two different domains.
+   */
+  public function testTwoDomains() {
+    $da = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
+    $db = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
+
+    $manager = $this->createManager();
+
+    $daSettings = $manager->getBagByDomain($da->id);
+    $daSettings->set('d1', 'un');
+    $this->assertEquals('un', $daSettings->get('d1'));
+    $this->assertEquals('beta', $daSettings->get('d2'));
+    $this->assertEquals('GAMMA!', $daSettings->get('d3'));
+
+    $dbSettings = $manager->getBagByDomain($db->id);
+    $this->assertEquals('alpha', $dbSettings->get('d1'));
+    $this->assertEquals('beta', $dbSettings->get('d2'));
+    $this->assertEquals('GAMMA!', $dbSettings->get('d3'));
+
+    $managerRedux = $this->createManager();
+
+    $daSettingsRedux = $managerRedux->getBagByDomain($da->id);
+    $this->assertEquals('un', $daSettingsRedux->get('d1'));
+    $this->assertEquals('beta', $daSettingsRedux->get('d2'));
+    $this->assertEquals('GAMMA!', $daSettingsRedux->get('d3'));
+  }
+
+  /**
+   * Test mingled reads/writes of settings for two different contacts.
+   */
+  public function testTwoContacts() {
+    $domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
+    $ca = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
+    $cb = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
+
+    $manager = $this->createManager();
+
+    $caSettings = $manager->getBagByContact($domain->id, $ca->id);
+    $caSettings->set('c1', 'un');
+    $this->assertEquals('un', $caSettings->get('c1'));
+    $this->assertEquals('beta', $caSettings->get('c2'));
+    $this->assertEquals('GAMMA MAN!', $caSettings->get('c3'));
+
+    $cbSettings = $manager->getBagByContact($domain->id, $cb->id);
+    $this->assertEquals('alpha', $cbSettings->get('c1'));
+    $this->assertEquals('beta', $cbSettings->get('c2'));
+    $this->assertEquals('GAMMA MAN!', $cbSettings->get('c3'));
+
+    // Read settings from freshly initialized objects.
+    $manager = $this->createManager();
+
+    $caSettingsRedux = $manager->getBagByContact($domain->id, $ca->id);
+    $this->assertEquals('un', $caSettingsRedux->get('c1'));
+    $this->assertEquals('beta', $caSettingsRedux->get('c2'));
+    $this->assertEquals('GAMMA MAN!', $caSettingsRedux->get('c3'));
+  }
+
+  public function testCrossOver() {
+    $domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
+    $contact = \CRM_Core_DAO::createTestObject('CRM_Contact_DAO_Contact');
+
+    $manager = $this->createManager();
+
+    // Store different values for the 'monkeywrench' setting on domain and contact
+
+    $domainSettings = $manager->getBagByDomain($domain->id);
+    $domainSettings->set('monkeywrench', 'from domain');
+    $this->assertEquals('from domain', $domainSettings->get('monkeywrench'));
+
+    $contactSettings = $manager->getBagByContact($domain->id, $contact->id);
+    $contactSettings->set('monkeywrench', 'from contact');
+    $this->assertEquals('from contact', $contactSettings->get('monkeywrench'));
+
+    // Read settings from freshly initialized objects.
+    $manager = $this->createManager();
+
+    $domainSettings = $manager->getBagByDomain($domain->id);
+    $this->assertEquals('from domain', $domainSettings->get('monkeywrench'));
+
+    $contactSettings = $manager->getBagByContact($domain->id, $contact->id);
+    $this->assertEquals('from contact', $contactSettings->get('monkeywrench'));
+  }
+
+  /**
+   * @return SettingsManager
+   */
+  protected function createManager() {
+    $cache = new \CRM_Utils_Cache_Arraycache(array());
+    $cache->set('defaults:domain', $this->domainDefaults);
+    $cache->set('defaults:contact', $this->contactDefaults);
+    $manager = new SettingsManager($cache, SettingsManager::parseMandatorySettings($this->mandates));
+    return $manager;
+  }
+
+}
index c935547cddbe541886af39a841f26f930bc0b50c..27bc07a8143ba5676a650538d12ef9820cbb8eec 100644 (file)
@@ -364,9 +364,11 @@ class api_v3_SettingTest extends CiviUnitTestCase {
     // the caching of data to all duplicates the caching of data to the empty string
     CRM_Core_BAO_Cache::setItem($data, 'CiviCRM setting Spec', 'All');
     CRM_Core_BAO_Cache::setItem($data, 'CiviCRM setting Specs', 'settingsMetadata__');
+    Civi::cache('settings')->flush();
     $fields = $this->callAPISuccess('setting', 'getfields', array('filters' => array('group_name' => 'Test Settings')));
     $this->assertArrayHasKey('test_key', $fields['values']);
     $this->callAPISuccess('setting', 'create', array('test_key' => 'keyset'));
+    $this->assertEquals('keyset', Civi::settings()->get('test_key'));
     $result = $this->callAPISuccess('setting', 'getvalue', array('name' => 'test_key', 'group' => 'Test Settings'));
     $this->assertEquals('keyset', $result);
   }
@@ -518,9 +520,9 @@ class api_v3_SettingTest extends CiviUnitTestCase {
   }
 
   /**
-   * Tests filling missing params.
+   * Settings should respect their defaults
    */
-  public function testFill() {
+  public function testDefaults() {
     $domparams = array(
       'name' => 'B Team Domain',
     );
@@ -540,7 +542,10 @@ class api_v3_SettingTest extends CiviUnitTestCase {
     );
     $result = $this->callAPISuccess('setting', 'get', $params);
     $this->assertAPISuccess($result, "in line " . __LINE__);
-    $this->assertArrayNotHasKey('tag_unconfirmed', $result['values'][$dom['id']], 'setting for domain 3 should not be set. Debug this IF domain test is passing');
+    $this->assertEquals('Unconfirmed', $result['values'][$dom['id']]['tag_unconfirmed']);
+
+    // The 'fill' operation is no longer necessary, but third parties might still use it, so let's
+    // make sure it doesn't do anything weird (crashing or breaking values).
     $result = $this->callAPISuccess('setting', 'fill', $params);
     $this->assertAPISuccess($result, "in line " . __LINE__);
     $result = $this->callAPISuccess('setting', 'get', $params);