Commit 3a84c0ab authored by totten's avatar totten

Civi::settings() - Add short hand for accessing domain settings

parent 7b5937fe
......@@ -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);
}
......
......@@ -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);
}
}
......@@ -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');
......
<?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();
//}
}
}
<?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;
}
}
<?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'));