Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-16395 Installation with localized default settings (more generic implementation) #8538

Closed
wants to merge 11 commits into from
100 changes: 73 additions & 27 deletions CRM/Admin/Form/Setting/Localization.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,40 +186,16 @@ public function postProcess() {
// we do this only to initialize monetary decimal point and thousand separator
$config = CRM_Core_Config::singleton();

// save enabled currencies and defaul currency in option group 'currencies_enabled'
// save enabled currencies and default currency in option group 'currencies_enabled'
// CRM-1496
if (empty($values['currencyLimit'])) {
$values['currencyLimit'] = array($values['defaultCurrency']);
}
elseif (!in_array($values['defaultCurrency'],
$values['currencyLimit']
)
) {
elseif (!in_array($values['defaultCurrency'], $values['currencyLimit'])) {
$values['currencyLimit'][] = $values['defaultCurrency'];
}

// sort so that when we display drop down, weights have right value
sort($values['currencyLimit']);

// get labels for all the currencies
$options = array();

$currencySymbols = self::getCurrencySymbols();
for ($i = 0; $i < count($values['currencyLimit']); $i++) {
$options[] = array(
'label' => $currencySymbols[$values['currencyLimit'][$i]],
'value' => $values['currencyLimit'][$i],
'weight' => $i + 1,
'is_active' => 1,
'is_default' => $values['currencyLimit'][$i] == $values['defaultCurrency'],
);
}

$dontCare = NULL;
CRM_Core_OptionGroup::createAssoc('currencies_enabled',
$options,
$dontCare
);
self::updateEnabledCurrencies($values['currencyLimit'], $values['defaultCurrency']);

// unset currencyLimit so we dont store there
unset($values['currencyLimit']);
Expand Down Expand Up @@ -264,6 +240,38 @@ public function postProcess() {
}
}


/**
* Replace available currencies by the ones provided
*
* @param $currencies array of currencies ['USD', 'CAD']
* @param $default default currency
*/
public static function updateEnabledCurrencies($currencies, $default) {

// sort so that when we display drop down, weights have right value
sort($currencies);

// get labels for all the currencies
$options = array();

$currencySymbols = \CRM_Admin_Form_Setting_Localization::getCurrencySymbols();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure the \ is needed here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, an unfortunate copy/paste

for ($i = 0; $i < count($currencies); $i++) {
$options[] = array(
'label' => $currencySymbols[$currencies[$i]],
'value' => $currencies[$i],
'weight' => $i + 1,
'is_active' => 1,
'is_default' => $currencies[$i] == $default,
);
}

$dontCare = NULL;
\CRM_Core_OptionGroup::createAssoc('currencies_enabled', $options, $dontCare);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likewise


}


/**
* @return array
*/
Expand Down Expand Up @@ -336,6 +344,44 @@ public static function onChangeLcMessages($oldLocale, $newLocale, $metadata, $do
}
}

public static function onChangeDefaultCurrency($oldCurrency, $newCurrency, $metadata) {
if ($oldCurrency == $newCurrency) {
return;
}

// ensure that default currency is always in the list of enabled currencies
$currencies = array_keys(CRM_Core_OptionGroup::values('currencies_enabled'));
if (!in_array($newCurrency, $currencies)) {
if (empty($currencies)) {
$currencies = array($values['defaultCurrency']);
}
else {
$currencies[] = $newCurrency;
}

// sort so that when we display drop down, weights have right value
sort($currencies);

// get labels for all the currencies
$options = array();

$currencySymbols = CRM_Admin_Form_Setting_Localization::getCurrencySymbols();
for ($i = 0; $i < count($currencies); $i++) {
$options[] = array(
'label' => $currencySymbols[$currencies[$i]],
'value' => $currencies[$i],
'weight' => $i + 1,
'is_active' => 1,
'is_default' => $currencies[$i] == $newCurrency,
);
}

$dontCare = NULL;
CRM_Core_OptionGroup::createAssoc('currencies_enabled', $options, $dontCare);
}

}

/**
* @return array
*/
Expand Down
32 changes: 32 additions & 0 deletions CRM/Core/BAO/OptionGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,36 @@ public static function ensureOptionGroupExists($params) {
}
}

/**
* Set the given values to active, and set all other values to inactive.
*
* @param string $optionGroupName
* e.g "languages"
* @param array<string> $activeValues
* e.g. array("en_CA","fr_CA")
*/
public static function setActiveValues($optionGroupName, $activeValues) {
$params = array(
1 => array($optionGroupName, 'String'),
);

// convert activeValues into placeholders / params in the query
$placeholders = array();
$i = count($params) + 1;
foreach ($activeValues as $value) {
$placeholders[] = "%{$i}";
$params[$i] = array($value, 'String');
$i++;
}
$placeholders = implode(', ', $placeholders);

CRM_Core_DAO::executeQuery("
UPDATE civicrm_option_value cov
LEFT JOIN civicrm_option_group cog ON cov.option_group_id = cog.id
SET cov.is_active = CASE WHEN cov.name IN ({$placeholders}) THEN 1 ELSE 0 END
WHERE cog.name = %1",
$params
);
}

}
12 changes: 11 additions & 1 deletion CRM/Core/I18n.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,22 @@ public static function languages($justEnabled = FALSE) {
if (!$all) {
$all = CRM_Contact_BAO_Contact::buildOptions('preferred_language');

// get labels
$rows = array();
$labels = array();
CRM_Core_OptionValue::getValues(array('name' => 'languages'), $rows);
foreach ($rows as $id => $row) {
$labels[$row['name']] = $row['label'];
}

// check which ones are available; add them to $all if not there already
$codes = array();
if (is_dir(CRM_Core_I18n::getResourceDir()) && $dir = opendir(CRM_Core_I18n::getResourceDir())) {
while ($filename = readdir($dir)) {
if (preg_match('/^[a-z][a-z]_[A-Z][A-Z]$/', $filename)) {
$codes[] = $filename;
if (!isset($all[$filename])) {
$all[$filename] = $filename;
$all[$filename] = $labels[$filename];
}
}
}
Expand All @@ -178,6 +186,8 @@ public static function languages($justEnabled = FALSE) {
unset($all[$code]);
}
}

ksort($all);
}

if ($enabled === NULL) {
Expand Down
1 change: 1 addition & 0 deletions Civi/Core/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ public function createEventDispatcher($container) {
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\InstallationCanary', 'check'));
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\DatabaseInitializer', 'initialize'));
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\LocalizationInitializer', 'initialize'));
$dispatcher->addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange'));
$dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange'));
$dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners'));
Expand Down
104 changes: 104 additions & 0 deletions Civi/Core/LocalizationInitializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?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;

use Civi;
use Civi\Core\Event\SystemInstallEvent;

/**
* Class LocalizationInitializer
* @package Civi\Core
*/
class LocalizationInitializer {

/**
* Load the locale settings based on the installation language
*
* @param \Civi\Core\Event\SystemInstallEvent $event
* @throws \CRM_Core_Exception
*/
public static function initialize(SystemInstallEvent $event) {

// get the current installation language
global $tsLocale;
$seedLanguage = $tsLocale;
if (!$seedLanguage) {
return;
}

// get the corresponding settings file if any
$localeDir = \CRM_Core_I18n::getResourceDir();
$fileName = $localeDir . $seedLanguage . DIRECTORY_SEPARATOR . 'settings.default.json';

// initalization
$settingsParams = array();

if (file_exists($fileName)) {

// load the file and parse it
$json = file_get_contents($fileName);
$settings = json_decode($json, TRUE);

if (!empty($settings)) {
// get all valid settings
$results = civicrm_api3('Setting', 'getfields', array());
$validSettings = array_keys($results['values']);
// add valid settings to params to send to api
foreach ($settings as $setting => $value) {
if (in_array($setting, $validSettings)) {
$settingsParams[$setting] = $value;
}

}

// ensure we don't mess with multilingual
unset($settingsParams['languageLimit']);

// support for enabled languages (option group)
if (isset($settings['languagesOption']) && count($settings['languagesOption']) > 0) {
\CRM_Core_BAO_OptionGroup::setActiveValues('languages', $settings['languagesOption']);
}

// set default currency in currencies_enabled (option group)
if (isset($settings['defaultCurrency'])) {
\CRM_Admin_Form_Setting_Localization::updateEnabledCurrencies(array($settings['defaultCurrency']), $settings['defaultCurrency']);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we're mapping 'settings.default.json into the Settings, but with exceptions. Taking a new look at this, I'm wondering about those exceptions.

  1. If setting languageLimit during installation is a bad idea, then wouldn't we just omit it from settings.default.json? (There's probably 100 settings which are irrelevant/bad-ideas for a localization package... but we don't need to blacklist all of them.) Any special reason to blacklist this one?
  2. For languageOptions, this seems to be preventing you from configuring other languages. (For example, in the screen Administer => Localization => Languages, wouldn't it hide a number of options?) Wouldn't this make it harder to setup multilingual? (Example: An NGO in France installs with the French language-pack, but it has branch-offices in Moracco, so it wants to enable multilingual with Arabic. But Arabic was disabled by the French language-pack.)
  3. Is updateCurrencies() something unique to installation process... or would it apply anytime you try to change defaultCurrency? If should apply anytime you update defaultCurrency, then perhaps it should be triggered this way:
diff --git a/settings/Localization.setting.php b/settings/Localization.setting.php
index e375d92..3b7eac6 100644
--- a/settings/Localization.setting.php
+++ b/settings/Localization.setting.php
@@ -142,6 +142,9 @@ return array(
  'defaultCurrency' => array(
    'group_name' => 'Localization Preferences',
    'group' => 'localization',
    'name' => 'defaultCurrency',
    'type' => 'String',
    'quick_form_type' => 'Select',
    'html_type' => 'Select',
    'html_attributes' => array(
      'class' => 'crm-select2',
    ),
    'default' => 'USD',
    'add' => '4.3',
    'title' => 'Default Currency',
    'is_domain' => 1,
    'is_contact' => 0,
    'description' => 'Default currency assigned to contributions and other monetary transactions.',
    'help_text' => NULL,
    'pseudoconstant' => array(
       'callback' => 'CRM_Admin_Form_Setting_Localization::getCurrencySymbols',
     ),
+    'on_change' => array(
+      'CRM_Foo_Bar::onChangeDefaultCurrency',
+    ),
   ),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@totten I'm looking at your comments again and :
2. You are right... disabling preferred languages prevents user from selecting them in the localization admin page. I think we should always have the full list of localizable languages in admin/settings/localization. Also, it might be a good idea to ensure the available languages are always in preferred language (using the on_change event you mentioned).
3. Very nice. I have done as you have proposed and it fixes an inconsistency between the UI and the api at the same time - i.e. ensure the default currency is always in the enabled currencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Oh, no, you can enable language that are not in the list of available preferred languages. But the label is lost in the admin localization setting page - so you get es_ES instead of Spanish and it gets to the bottom of the list. I have fixed this bug.
  2. Except that there is no settings for currency_enabled. I have to manually initialize to the same as default language. The logic applies only in the installation process. So i guess i need the code after all. To avoid duplication of code, I have moved it to CRM_Admin_Form_Setting_Localization. We might want to have an simple api call for updating currency_enabled but it's not in the scope of this issue.

}

}

// in any case, enforce the seedLanguage as the default language
$settingsParams['lcMessages'] = $seedLanguage;

// apply the config
civicrm_api3('Setting', 'create', $settingsParams);

}

}
17 changes: 7 additions & 10 deletions install/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
}
}

// Set the locale (required by CRM_Core_Config)
// Set the CMS
// This is mostly sympbolic, since nothing we do during the install
// really requires CIVICRM_UF to be defined.
$installTypeToUF = array(
Expand All @@ -189,6 +189,7 @@
$uf = (isset($installTypeToUF[$installType]) ? $installTypeToUF[$installType] : 'Drupal');
define('CIVICRM_UF', $uf);

// Set the Locale (required by CRM_Core_Config)
global $tsLocale;

$tsLocale = 'en_US';
Expand Down Expand Up @@ -1487,6 +1488,11 @@ public function install($config) {
// now enable civicrm module.
module_enable(array('civicrm', 'civicrmtheme'));

// SystemInstallEvent will be call from here with the first call of CiviCRM_Core_Settings
// we need to pass the seedLanguage before that
global $civicrm_setting;
$civicrm_setting['domain']['lcMessages'] = $config['seedLanguage'];

// clear block, page, theme, and hook caches
drupal_flush_all_caches();

Expand All @@ -1497,15 +1503,6 @@ public function install($config) {
$GLOBALS['user'] = $original_user;
drupal_save_session(TRUE);

//change the default language to one chosen
if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
civicrm_api3('Setting', 'create', array(
'domain_id' => 'current_domain',
'lcMessages' => $config['seedLanguage'],
)
);
}

$output .= '</ul>';
$output .= '</div>';
$output .= '</body>';
Expand Down
3 changes: 3 additions & 0 deletions settings/Localization.setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@
'pseudoconstant' => array(
'callback' => 'CRM_Admin_Form_Setting_Localization::getCurrencySymbols',
),
'on_change' => array(
'CRM_Admin_Form_Setting_Localization::onChangeDefaultCurrency',
),
),
'defaultContactCountry' => array(
'group_name' => 'Localization Preferences',
Expand Down