3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
35 * This trait allows us to consolidate Preferences & Settings forms.
37 * It is intended mostly as part of a refactoring process to get rid of having 2.
39 trait CRM_Admin_Form_SettingTrait
{
42 * The setting page filter.
51 protected $settingsMetadata;
58 public function getDefaultEntity() {
63 * Get the metadata relating to the settings on the form, ordered by the keys in $this->_settings.
67 protected function getSettingsMetaData() {
68 if (empty($this->settingsMetadata
)) {
69 $this->settingsMetadata
= \Civi\Core\SettingsMetadata
::getMetadata(['name' => array_keys($this->_settings
)], NULL, TRUE);
70 // This array_merge re-orders to the key order of $this->_settings.
71 $this->settingsMetadata
= array_merge($this->_settings
, $this->settingsMetadata
);
73 return $this->settingsMetadata
;
77 * Get the settings which can be stored based on metadata.
79 * @param array $params
82 protected function getSettingsToSetByMetadata($params) {
83 $setValues = array_intersect_key($params, $this->_settings
);
84 // Checkboxes will be unset rather than empty so we need to add them back in.
85 // Handle quickform hateability just once, right here right now.
86 $unsetValues = array_diff_key($this->_settings
, $params);
87 foreach ($unsetValues as $key => $unsetValue) {
88 if ($this->getQuickFormType($this->getSettingMetadata($key)) === 'CheckBox') {
89 $setValues[$key] = [$key => 0];
98 protected function filterParamsSetByMetadata(&$params) {
99 foreach ($this->getSettingsToSetByMetadata($params) as $setting => $settingGroup) {
100 //@todo array_diff this
101 unset($params[$setting]);
106 * Get the metadata for a particular field.
111 protected function getSettingMetadata($setting) {
112 return $this->getSettingsMetaData()[$setting];
116 * Get the metadata for a particular field for a particular item.
118 * e.g get 'serialize' key, if exists, for a field.
124 protected function getSettingMetadataItem($setting, $item) {
125 return CRM_Utils_Array
::value($item, $this->getSettingsMetaData()[$setting]);
131 protected function getSettingPageFilter() {
132 if (!isset($this->_filter
)) {
133 // Get the last URL component without modifying the urlPath property.
134 $urlPath = array_values($this->urlPath
);
135 $this->_filter
= end($urlPath);
137 return $this->_filter
;
141 * Returns a re-keyed copy of the settings, ordered by weight.
145 protected function getSettingsOrderedByWeight() {
146 $settingMetaData = $this->getSettingsMetaData();
147 $filter = $this->getSettingPageFilter();
149 usort($settingMetaData, function ($a, $b) use ($filter) {
150 // Handle cases in which a comparison is impossible. Such will be considered ties.
152 // A comparison can't be made unless both setting weights are declared.
153 !isset($a['settings_pages'][$filter]['weight'], $b['settings_pages'][$filter]['weight'])
154 // A pair of settings might actually have the same weight.
155 ||
$a['settings_pages'][$filter]['weight'] === $b['settings_pages'][$filter]['weight']
160 return $a['settings_pages'][$filter]['weight'] > $b['settings_pages'][$filter]['weight'] ?
1 : -1;
163 return $settingMetaData;
167 * Add fields in the metadata to the template.
169 protected function addFieldsDefinedInSettingsMetadata() {
170 $settingMetaData = $this->getSettingsMetaData();
172 foreach ($settingMetaData as $setting => $props) {
173 $quickFormType = $this->getQuickFormType($props);
174 if (isset($quickFormType)) {
175 $options = CRM_Utils_Array
::value('options', $props);
177 if ($props['html_type'] === 'Select' && isset($props['is_required']) && $props['is_required'] === FALSE && !isset($options[''])) {
178 // If the spec specifies the field is not required add a null option.
179 // Why not if empty($props['is_required']) - basically this has been added to the spec & might not be set to TRUE
181 $options = ['' => ts('None')] +
$options;
184 if ($props['type'] === 'Boolean') {
185 $options = [$props['title'] => $props['name']];
188 //Load input as readonly whose values are overridden in civicrm.settings.php.
189 if (Civi
::settings()->getMandatory($setting)) {
190 $props['html_attributes']['readonly'] = TRUE;
191 $this->includesReadOnlyFields
= TRUE;
194 $add = 'add' . $quickFormType;
195 if ($add == 'addElement') {
200 ($options !== NULL) ?
$options : CRM_Utils_Array
::value('html_attributes', $props, []),
201 ($options !== NULL) ? CRM_Utils_Array
::value('html_attributes', $props, []) : NULL
204 elseif ($add == 'addSelect') {
205 $this->addElement('select', $setting, ts($props['title']), $options, CRM_Utils_Array
::value('html_attributes', $props));
207 elseif ($add == 'addCheckBox') {
208 $this->addCheckBox($setting, '', $options, NULL, CRM_Utils_Array
::value('html_attributes', $props), NULL, NULL, [' ']);
210 elseif ($add == 'addCheckBoxes') {
211 $newOptions = array_flip($options);
212 $classes = 'crm-checkbox-list';
213 if (!empty($props['sortable'])) {
214 $classes .= ' crm-sortable-list';
215 $newOptions = array_flip(self
::reorderSortableOptions($setting, $options));
217 $settingMetaData[$setting]['wrapper_element'] = ['<ul class="' . $classes . '"><li>', '</li></ul>'];
218 $this->addCheckBox($setting,
221 NULL, NULL, NULL, NULL,
225 elseif ($add == 'addChainSelect') {
226 $this->addChainSelect($setting, [
227 'label' => ts($props['title']),
230 elseif ($add == 'addMonthDay') {
231 $this->add('date', $setting, ts($props['title']), CRM_Core_SelectValues
::date(NULL, 'M d'));
233 elseif ($add === 'addEntityRef') {
234 $this->$add($setting, ts($props['title']), $props['entity_reference_options']);
236 elseif ($add === 'addYesNo' && ($props['type'] === 'Boolean')) {
237 $this->addRadio($setting, ts($props['title']), [1 => 'Yes', 0 => 'No'], NULL, ' ');
239 elseif ($add === 'add') {
240 $this->add($props['html_type'], $setting, ts($props['title']), $options);
243 $this->$add($setting, ts($props['title']), $options);
245 // Migrate to using an array as easier in smart...
246 $description = CRM_Utils_Array
::value('description', $props);
247 $descriptions[$setting] = $description;
248 $this->assign("{$setting}_description", $description);
249 if ($setting == 'max_attachments') {
250 //temp hack @todo fix to get from metadata
251 $this->addRule('max_attachments', ts('Value should be a positive number'), 'positiveInteger');
253 if ($setting == 'maxFileSize') {
255 $this->addRule('maxFileSize', ts('Value should be a positive number'), 'positiveInteger');
260 // setting_description should be deprecated - see Mail.tpl for metadata based tpl.
261 $this->assign('setting_descriptions', $descriptions);
262 $this->assign('settings_fields', $settingMetaData);
263 $this->assign('fields', $this->getSettingsOrderedByWeight());
267 * Get the quickform type for the given html type.
273 protected function getQuickFormType($spec) {
274 if (isset($spec['quick_form_type']) &&
275 !($spec['quick_form_type'] === 'Element' && !empty($spec['html_type']))) {
276 // This is kinda transitional
277 return $spec['quick_form_type'];
280 // The spec for settings has been updated for consistency - we provide deprecation notices for sites that have
281 // not made this change.
282 $htmlType = $spec['html_type'];
283 if ($htmlType !== strtolower($htmlType)) {
284 CRM_Core_Error
::deprecatedFunctionWarning(ts('Settings fields html_type should be lower case - see https://docs.civicrm.org/dev/en/latest/framework/setting/ - this needs to be fixed for ' . $spec['name']));
285 $htmlType = strtolower($spec['html_type']);
288 'checkboxes' => 'CheckBoxes',
289 'checkbox' => 'CheckBox',
291 'select' => 'Select',
292 'textarea' => 'Element',
294 'entity_reference' => 'EntityRef',
295 'advmultiselect' => 'Element',
297 $mapping +
= array_fill_keys(CRM_Core_Form
::$html5Types, '');
298 return $mapping[$htmlType];
302 * Get the defaults for all fields defined in the metadata.
304 * All others are pending conversion.
306 protected function setDefaultsForMetadataDefinedFields() {
307 CRM_Core_BAO_ConfigSetting
::retrieve($this->_defaults
);
308 foreach (array_keys($this->_settings
) as $setting) {
309 $this->_defaults
[$setting] = civicrm_api3('setting', 'getvalue', ['name' => $setting]);
310 $spec = $this->getSettingsMetadata()[$setting];
311 if (!empty($spec['serialize'])) {
312 $this->_defaults
[$setting] = CRM_Core_DAO
::unSerializeField($this->_defaults
[$setting], $spec['serialize']);
314 if ($this->getQuickFormType($spec) === 'CheckBoxes') {
315 $this->_defaults
[$setting] = array_fill_keys($this->_defaults
[$setting], 1);
317 if ($this->getQuickFormType($spec) === 'CheckBox') {
318 $this->_defaults
[$setting] = [$setting => $this->_defaults
[$setting]];
324 * Save any fields which have been defined via metadata.
326 * (Other fields are hack-handled... sadly.
328 * @param array $params
331 * @throws \CiviCRM_API3_Exception
333 protected function saveMetadataDefinedSettings($params) {
334 $settings = $this->getSettingsToSetByMetadata($params);
335 foreach ($settings as $setting => $settingValue) {
336 $settingMetaData = $this->getSettingMetadata($setting);
337 if (!empty($settingMetaData['sortable'])) {
338 $settings[$setting] = $this->getReorderedSettingData($setting, $settingValue);
340 elseif ($this->getQuickFormType($settingMetaData) === 'CheckBoxes') {
341 $settings[$setting] = array_keys($settingValue);
343 elseif ($this->getQuickFormType($settingMetaData) === 'CheckBox') {
344 // This will be an array with one value.
345 $settings[$setting] = (int) reset($settings[$setting]);
348 civicrm_api3('setting', 'create', $settings);
352 * Display options in correct order on the form
358 public static function reorderSortableOptions($setting, $options) {
359 return array_merge(array_flip(Civi
::settings()->get($setting)), $options);
363 * @param string $setting
364 * @param array $settingValue
368 private function getReorderedSettingData($setting, $settingValue) {
369 // Get order from $_POST as $_POST maintains the order the sorted setting
370 // options were sent. You can simply assign data from $_POST directly to
371 // $settings[] but preference has to be given to data from Quickform.
372 $order = array_keys(\CRM_Utils_Request
::retrieve($setting, 'String'));
373 $settingValueKeys = array_keys($settingValue);
374 return array_intersect($order, $settingValueKeys);