3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * This trait allows us to consolidate Preferences & Settings forms.
21 * It is intended mostly as part of a refactoring process to get rid of having 2.
23 trait CRM_Admin_Form_SettingTrait
{
26 * The setting page filter.
35 protected $settingsMetadata;
42 public function getDefaultEntity() {
47 * Get the metadata relating to the settings on the form, ordered by the keys in $this->_settings.
51 protected function getSettingsMetaData() {
52 if (empty($this->settingsMetadata
)) {
53 $this->settingsMetadata
= \Civi\Core\SettingsMetadata
::getMetadata(['name' => array_keys($this->_settings
)], NULL, TRUE);
54 // This array_merge re-orders to the key order of $this->_settings.
55 $this->settingsMetadata
= array_merge($this->_settings
, $this->settingsMetadata
);
57 return $this->settingsMetadata
;
61 * Get the settings which can be stored based on metadata.
63 * @param array $params
66 protected function getSettingsToSetByMetadata($params) {
67 $setValues = array_intersect_key($params, $this->_settings
);
68 // Checkboxes will be unset rather than empty so we need to add them back in.
69 // Handle quickform hateability just once, right here right now.
70 $unsetValues = array_diff_key($this->_settings
, $params);
71 foreach ($unsetValues as $key => $unsetValue) {
72 if ($this->getQuickFormType($this->getSettingMetadata($key)) === 'CheckBox') {
73 $setValues[$key] = [$key => 0];
82 protected function filterParamsSetByMetadata(&$params) {
83 foreach ($this->getSettingsToSetByMetadata($params) as $setting => $settingGroup) {
84 //@todo array_diff this
85 unset($params[$setting]);
90 * Get the metadata for a particular field.
95 protected function getSettingMetadata($setting) {
96 return $this->getSettingsMetaData()[$setting];
100 * Get the metadata for a particular field for a particular item.
102 * e.g get 'serialize' key, if exists, for a field.
108 protected function getSettingMetadataItem($setting, $item) {
109 return CRM_Utils_Array
::value($item, $this->getSettingsMetaData()[$setting]);
113 * This is public so we can retrieve the filter name via hooks etc. and apply conditional logic (eg. loading javascript conditionals).
117 public function getSettingPageFilter() {
118 if (!isset($this->_filter
)) {
119 // Get the last URL component without modifying the urlPath property.
120 $urlPath = array_values($this->urlPath
);
121 $this->_filter
= end($urlPath);
123 return $this->_filter
;
127 * Returns a re-keyed copy of the settings, ordered by weight.
131 protected function getSettingsOrderedByWeight() {
132 $settingMetaData = $this->getSettingsMetaData();
133 $filter = $this->getSettingPageFilter();
135 usort($settingMetaData, function ($a, $b) use ($filter) {
136 // Handle cases in which a comparison is impossible. Such will be considered ties.
138 // A comparison can't be made unless both setting weights are declared.
139 !isset($a['settings_pages'][$filter]['weight'], $b['settings_pages'][$filter]['weight'])
140 // A pair of settings might actually have the same weight.
141 ||
$a['settings_pages'][$filter]['weight'] === $b['settings_pages'][$filter]['weight']
146 return $a['settings_pages'][$filter]['weight'] > $b['settings_pages'][$filter]['weight'] ?
1 : -1;
149 return $settingMetaData;
153 * Add fields in the metadata to the template.
155 protected function addFieldsDefinedInSettingsMetadata() {
156 $settingMetaData = $this->getSettingsMetaData();
158 foreach ($settingMetaData as $setting => $props) {
159 $quickFormType = $this->getQuickFormType($props);
160 if (isset($quickFormType)) {
161 $options = CRM_Utils_Array
::value('options', $props);
163 if ($props['html_type'] === 'Select' && isset($props['is_required']) && $props['is_required'] === FALSE && !isset($options[''])) {
164 // If the spec specifies the field is not required add a null option.
165 // Why not if empty($props['is_required']) - basically this has been added to the spec & might not be set to TRUE
167 $options = ['' => ts('None')] +
$options;
170 if ($props['type'] === 'Boolean') {
171 $options = [$props['title'] => $props['name']];
174 //Load input as readonly whose values are overridden in civicrm.settings.php.
175 if (Civi
::settings()->getMandatory($setting)) {
176 $props['html_attributes']['readonly'] = TRUE;
177 $this->includesReadOnlyFields
= TRUE;
180 $add = 'add' . $quickFormType;
181 if ($add == 'addElement') {
186 ($options !== NULL) ?
$options : CRM_Utils_Array
::value('html_attributes', $props, []),
187 ($options !== NULL) ? CRM_Utils_Array
::value('html_attributes', $props, []) : NULL
190 elseif ($add == 'addSelect') {
191 $this->addElement('select', $setting, $props['title'], $options, CRM_Utils_Array
::value('html_attributes', $props));
193 elseif ($add == 'addCheckBox') {
194 $this->addCheckBox($setting, '', $options, NULL, CRM_Utils_Array
::value('html_attributes', $props), NULL, NULL, [' ']);
196 elseif ($add == 'addCheckBoxes') {
197 $newOptions = array_flip($options);
198 $classes = 'crm-checkbox-list';
199 if (!empty($props['sortable'])) {
200 $classes .= ' crm-sortable-list';
201 $newOptions = array_flip(self
::reorderSortableOptions($setting, $options));
203 $settingMetaData[$setting]['wrapper_element'] = ['<ul class="' . $classes . '"><li>', '</li></ul>'];
204 $this->addCheckBox($setting,
207 NULL, NULL, NULL, NULL,
211 elseif ($add == 'addChainSelect') {
212 $this->addChainSelect($setting, [
213 'label' => $props['title'],
216 elseif ($add == 'addMonthDay') {
217 $this->add('date', $setting, $props['title'], CRM_Core_SelectValues
::date(NULL, 'M d'));
219 elseif ($add === 'addEntityRef') {
220 $this->$add($setting, $props['title'], $props['entity_reference_options']);
222 elseif ($add === 'addYesNo' && ($props['type'] === 'Boolean')) {
223 $this->addRadio($setting, $props['title'], [1 => 'Yes', 0 => 'No'], NULL, ' ');
225 elseif ($add === 'add') {
226 $this->add($props['html_type'], $setting, $props['title'], $options);
229 $this->$add($setting, $props['title'], $options);
231 // Migrate to using an array as easier in smart...
232 $description = CRM_Utils_Array
::value('description', $props);
233 $descriptions[$setting] = $description;
234 $this->assign("{$setting}_description", $description);
235 if ($setting == 'max_attachments') {
236 //temp hack @todo fix to get from metadata
237 $this->addRule('max_attachments', ts('Value should be a positive number'), 'positiveInteger');
239 if ($setting == 'max_attachments_backend') {
240 //temp hack @todo fix to get from metadata
241 $this->addRule('max_attachments_backend', ts('Value should be a positive number'), 'positiveInteger');
243 if ($setting == 'maxFileSize') {
245 $this->addRule('maxFileSize', ts('Value should be a positive number'), 'positiveInteger');
250 // setting_description should be deprecated - see Mail.tpl for metadata based tpl.
251 $this->assign('setting_descriptions', $descriptions);
252 $this->assign('settings_fields', $settingMetaData);
253 $this->assign('fields', $this->getSettingsOrderedByWeight());
257 * Get the quickform type for the given html type.
263 protected function getQuickFormType($spec) {
264 if (isset($spec['quick_form_type']) &&
265 !($spec['quick_form_type'] === 'Element' && !empty($spec['html_type']))) {
266 // This is kinda transitional
267 return $spec['quick_form_type'];
270 // The spec for settings has been updated for consistency - we provide deprecation notices for sites that have
271 // not made this change.
272 $htmlType = $spec['html_type'];
273 if ($htmlType !== strtolower($htmlType)) {
274 // Avoiding 'ts' for obscure strings.
275 CRM_Core_Error
::deprecatedFunctionWarning('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']);
276 $htmlType = strtolower($spec['html_type']);
279 'checkboxes' => 'CheckBoxes',
280 'checkbox' => 'CheckBox',
282 'select' => 'Select',
283 'textarea' => 'Element',
285 'entity_reference' => 'EntityRef',
286 'advmultiselect' => 'Element',
288 $mapping +
= array_fill_keys(CRM_Core_Form
::$html5Types, '');
289 return $mapping[$htmlType];
293 * Get the defaults for all fields defined in the metadata.
295 * All others are pending conversion.
297 protected function setDefaultsForMetadataDefinedFields() {
298 CRM_Core_BAO_ConfigSetting
::retrieve($this->_defaults
);
299 foreach (array_keys($this->_settings
) as $setting) {
300 $this->_defaults
[$setting] = civicrm_api3('setting', 'getvalue', ['name' => $setting]);
301 $spec = $this->getSettingsMetadata()[$setting];
302 if (!empty($spec['serialize'])) {
303 $this->_defaults
[$setting] = CRM_Core_DAO
::unSerializeField($this->_defaults
[$setting], $spec['serialize']);
305 if ($this->getQuickFormType($spec) === 'CheckBoxes') {
306 $this->_defaults
[$setting] = array_fill_keys($this->_defaults
[$setting], 1);
308 if ($this->getQuickFormType($spec) === 'CheckBox') {
309 $this->_defaults
[$setting] = [$setting => $this->_defaults
[$setting]];
315 * Save any fields which have been defined via metadata.
317 * (Other fields are hack-handled... sadly.
319 * @param array $params
322 * @throws \CiviCRM_API3_Exception
324 protected function saveMetadataDefinedSettings($params) {
325 $settings = $this->getSettingsToSetByMetadata($params);
326 foreach ($settings as $setting => $settingValue) {
327 $settingMetaData = $this->getSettingMetadata($setting);
328 if (!empty($settingMetaData['sortable'])) {
329 $settings[$setting] = $this->getReorderedSettingData($setting, $settingValue);
331 elseif ($this->getQuickFormType($settingMetaData) === 'CheckBoxes') {
332 $settings[$setting] = array_keys($settingValue);
334 elseif ($this->getQuickFormType($settingMetaData) === 'CheckBox') {
335 // This will be an array with one value.
336 $settings[$setting] = (int) reset($settings[$setting]);
339 civicrm_api3('setting', 'create', $settings);
343 * Display options in correct order on the form
349 public static function reorderSortableOptions($setting, $options) {
350 return array_merge(array_flip(Civi
::settings()->get($setting)), $options);
354 * @param string $setting
355 * @param array $settingValue
359 private function getReorderedSettingData($setting, $settingValue) {
360 // Get order from $_POST as $_POST maintains the order the sorted setting
361 // options were sent. You can simply assign data from $_POST directly to
362 // $settings[] but preference has to be given to data from Quickform.
363 $order = array_keys(\CRM_Utils_Request
::retrieve($setting, 'String'));
364 $settingValueKeys = array_keys($settingValue);
365 return array_intersect($order, $settingValueKeys);