I was able to achieve this by using the set_updatedcallback() method.
In /local/my_plugin/settings.php:
if ($hassiteconfig) {
$settings = new admin_settingpage('local_foo', get_string('pluginname', 'local_foo'));
$ADMIN->add('localplugins', $settings);
$fields = array('...');
foreach ($fields as $field) {
$setting = new admin_setting_whatever("{$field}_name", '...');
$setting->set_updatedcallback('local_foo_save_values');
$settings->add($setting);
$setting = new admin_setting_whatever("{$field}_type", '...');
$setting->set_updatedcallback('local_foo_save_values');
$settings->add($setting);
}
}
In /local/my_plugin/lib.php:
function local_foo_save_values() {
static $is_processed;
if ($is_processed) {
// Run only once per request
return;
}
$fields = array('...');
$config = array();
foreach ($fields as $field) {
$settings = new stdClass();
$setting->name = required_param("{$field}_name", PARAM_RAW);
$setting->type = required_param("{$field}_type", PARAM_RAW);
$config[$field][] = $settings;
}
set_config('fields', json_encode($config), 'local_foo');
$is_processed = true;
}
The approach is quite hacky, but it does the trick.