Commit 037273d8 authored by Stephen Bourget's avatar Stephen Bourget
Browse files

MDL-12689: convert auth plugins to use settings.php

parent 216ea39b
......@@ -16,6 +16,9 @@ $err = array();
$returnurl = "$CFG->wwwroot/$CFG->admin/settings.php?section=manageauths";
debugging("Use of config.html files in authentication plugins have been depreciated. " .
" Please migrate your plugin to use the admin settings API", DEBUG_DEVELOPER);
// save configuration changes
if ($frm = data_submitted() and confirm_sesskey()) {
......@@ -86,12 +89,28 @@ exit;
/// Functions /////////////////////////////////////////////////////////////////
// Good enough for most auth plugins
// but some may want a custom one if they are offering
// other options
// Note: lockconfig_ fields have special handling.
/**
* auth field locking
* Good enough for most auth plugins
* but some may want a custom one if they are offering
* other options
* Note: lockconfig_ fields have special handling.
*
* @param string $auth authentication plugin shortname
* @param array $user_fields user profile fields
* @param string $helptext help text to be displayed at top of form
* @param boolean $retrieveopts Map fields or lock only.
* @param boolean $updateopts Allow remote updates
* @param array $customfields list of custom profile fields
* @deprecated since Moodle 3.3
*/
function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts, $updateopts, $customfields = array()) {
global $DB, $OUTPUT;
debugging("The function 'print_auth_lock_options' has been depreciated, " .
"Please migrate your code to use the admin settings API and use the function 'display_auth_lock_options' instead. ",
DEBUG_DEVELOPER);
echo '<tr><td colspan="3">';
if ($retrieveopts) {
echo $OUTPUT->heading(get_string('auth_data_mapping', 'auth'));
......
......@@ -230,47 +230,6 @@ class auth_plugin_cas extends auth_plugin_ldap {
}
}
/**
* Prints a form for configuring this authentication plugin.
*
* This function is called from admin/auth.php, and outputs a full page with
* a form for configuring this plugin.
*
* @param array $page An object containing all the data for this page.
*/
function config_form($config, $err, $user_fields) {
global $CFG, $OUTPUT;
if (!function_exists('ldap_connect')) { // Is php-ldap really there?
echo $OUTPUT->notification(get_string('auth_ldap_noextension', 'auth_ldap'));
// Don't return here, like we do in auth/ldap. We cas use CAS without LDAP.
// So just warn the user (done above) and define the LDAP constants we use
// in config.html, to silence the warnings.
if (!defined('LDAP_DEREF_NEVER')) {
define ('LDAP_DEREF_NEVER', 0);
}
if (!defined('LDAP_DEREF_ALWAYS')) {
define ('LDAP_DEREF_ALWAYS', 3);
}
}
include($CFG->dirroot.'/auth/cas/config.html');
}
/**
* A chance to validate form data, and last chance to
* do stuff before it is inserted in config_plugin
* @param object object with submitted configuration settings (without system magic quotes)
* @param array $err array of error messages
*/
function validate_form($form, &$err) {
$certificate_path = trim($form->certificate_path);
if ($form->certificate_check && empty($certificate_path)) {
$err['certificate_path'] = get_string('auth_cas_certificate_path_empty', 'auth_cas');
}
}
/**
* Returns the URL for changing the user's pw, or empty if the default can
* be used.
......@@ -281,143 +240,6 @@ class auth_plugin_cas extends auth_plugin_ldap {
return null;
}
/**
* Processes and stores configuration data for this authentication plugin.
*/
function process_config($config) {
// CAS settings
if (!isset($config->hostname)) {
$config->hostname = '';
}
if (!isset($config->port)) {
$config->port = '';
}
if (!isset($config->casversion)) {
$config->casversion = '';
}
if (!isset($config->baseuri)) {
$config->baseuri = '';
}
if (!isset($config->language)) {
$config->language = '';
}
if (!isset($config->proxycas)) {
$config->proxycas = '';
}
if (!isset($config->logoutcas)) {
$config->logoutcas = '';
}
if (!isset($config->multiauth)) {
$config->multiauth = '';
}
if (!isset($config->certificate_check)) {
$config->certificate_check = '';
}
if (!isset($config->certificate_path)) {
$config->certificate_path = '';
}
if (!isset($config->curl_ssl_version)) {
$config->curl_ssl_version = '';
}
if (!isset($config->logout_return_url)) {
$config->logout_return_url = '';
}
// LDAP settings
if (!isset($config->host_url)) {
$config->host_url = '';
}
if (!isset($config->start_tls)) {
$config->start_tls = false;
}
if (empty($config->ldapencoding)) {
$config->ldapencoding = 'utf-8';
}
if (!isset($config->pagesize)) {
$config->pagesize = LDAP_DEFAULT_PAGESIZE;
}
if (!isset($config->contexts)) {
$config->contexts = '';
}
if (!isset($config->user_type)) {
$config->user_type = 'default';
}
if (!isset($config->user_attribute)) {
$config->user_attribute = '';
}
if (!isset($config->search_sub)) {
$config->search_sub = '';
}
if (!isset($config->opt_deref)) {
$config->opt_deref = LDAP_DEREF_NEVER;
}
if (!isset($config->bind_dn)) {
$config->bind_dn = '';
}
if (!isset($config->bind_pw)) {
$config->bind_pw = '';
}
if (!isset($config->ldap_version)) {
$config->ldap_version = '3';
}
if (!isset($config->objectclass)) {
$config->objectclass = '';
}
if (!isset($config->memberattribute)) {
$config->memberattribute = '';
}
if (!isset($config->memberattribute_isdn)) {
$config->memberattribute_isdn = '';
}
if (!isset($config->attrcreators)) {
$config->attrcreators = '';
}
if (!isset($config->groupecreators)) {
$config->groupecreators = '';
}
if (!isset($config->removeuser)) {
$config->removeuser = AUTH_REMOVEUSER_KEEP;
}
// save CAS settings
set_config('hostname', trim($config->hostname), $this->pluginconfig);
set_config('port', trim($config->port), $this->pluginconfig);
set_config('casversion', $config->casversion, $this->pluginconfig);
set_config('baseuri', trim($config->baseuri), $this->pluginconfig);
set_config('language', $config->language, $this->pluginconfig);
set_config('proxycas', $config->proxycas, $this->pluginconfig);
set_config('logoutcas', $config->logoutcas, $this->pluginconfig);
set_config('multiauth', $config->multiauth, $this->pluginconfig);
set_config('certificate_check', $config->certificate_check, $this->pluginconfig);
set_config('certificate_path', $config->certificate_path, $this->pluginconfig);
set_config('curl_ssl_version', $config->curl_ssl_version, $this->pluginconfig);
set_config('logout_return_url', $config->logout_return_url, $this->pluginconfig);
// save LDAP settings
set_config('host_url', trim($config->host_url), $this->pluginconfig);
set_config('start_tls', $config->start_tls, $this->pluginconfig);
set_config('ldapencoding', trim($config->ldapencoding), $this->pluginconfig);
set_config('pagesize', (int)trim($config->pagesize), $this->pluginconfig);
set_config('contexts', trim($config->contexts), $this->pluginconfig);
set_config('user_type', core_text::strtolower(trim($config->user_type)), $this->pluginconfig);
set_config('user_attribute', core_text::strtolower(trim($config->user_attribute)), $this->pluginconfig);
set_config('search_sub', $config->search_sub, $this->pluginconfig);
set_config('opt_deref', $config->opt_deref, $this->pluginconfig);
set_config('bind_dn', trim($config->bind_dn), $this->pluginconfig);
set_config('bind_pw', $config->bind_pw, $this->pluginconfig);
set_config('ldap_version', $config->ldap_version, $this->pluginconfig);
set_config('objectclass', trim($config->objectclass), $this->pluginconfig);
set_config('memberattribute', core_text::strtolower(trim($config->memberattribute)), $this->pluginconfig);
set_config('memberattribute_isdn', $config->memberattribute_isdn, $this->pluginconfig);
set_config('attrcreators', trim($config->attrcreators), $this->pluginconfig);
set_config('groupecreators', trim($config->groupecreators), $this->pluginconfig);
set_config('removeuser', $config->removeuser, $this->pluginconfig);
return true;
}
/**
* Returns true if user should be coursecreator.
*
......
This diff is collapsed.
......@@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
/**
* Function to upgrade auth_cas.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
......@@ -58,5 +59,11 @@ function xmldb_auth_cas_upgrade($oldversion) {
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/cas to auth_cas.
$DB->set_field('config_plugins', 'plugin', 'auth_cas', array('plugin' => 'auth/cas'));
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'cas');
}
return true;
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults.
*
* @package auth_cas
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_lowercase_configtext.php');
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_contexts_configtext.php');
// Include needed files.
require_once($CFG->dirroot.'/auth/cas/auth.php');
require_once($CFG->dirroot.'/auth/cas/languages.php');
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_cas/pluginname', '',
new lang_string('auth_casdescription', 'auth_cas')));
// CAS server configuration label.
$settings->add(new admin_setting_heading('auth_cas/casserversettings',
new lang_string('auth_cas_server_settings', 'auth_cas'), ''));
// Hostname.
$settings->add(new admin_setting_configtext('auth_cas/hostname',
get_string('auth_cas_hostname_key', 'auth_cas'),
get_string('auth_cas_hostname', 'auth_cas'), '', PARAM_RAW_TRIMMED));
// Base URI.
$settings->add(new admin_setting_configtext('auth_cas/baseuri',
get_string('auth_cas_baseuri_key', 'auth_cas'),
get_string('auth_cas_baseuri', 'auth_cas'), '', PARAM_RAW_TRIMMED));
// Port.
$settings->add(new admin_setting_configtext('auth_cas/port',
get_string('auth_cas_port_key', 'auth_cas'),
get_string('auth_cas_port', 'auth_cas'), '', PARAM_INT));
// CAS Version.
$casversions = array();
$casversions[CAS_VERSION_1_0] = 'CAS 1.0';
$casversions[CAS_VERSION_2_0] = 'CAS 2.0';
$settings->add(new admin_setting_configselect('auth_cas/casversion',
new lang_string('auth_cas_casversion', 'auth_cas'),
new lang_string('auth_cas_version', 'auth_cas'), CAS_VERSION_2_0, $casversions));
// Language.
if (!isset($CASLANGUAGES) || empty($CASLANGUAGES)) {
// Prevent warnings on other admin pages.
// $CASLANGUAGES is defined in /auth/cas/languages.php.
$CASLANGUAGES = array();
$CASLANGUAGES[PHPCAS_LANG_ENGLISH] = 'English';
$CASLANGUAGES[PHPCAS_LANG_FRENCH] = 'French';
}
$settings->add(new admin_setting_configselect('auth_cas/language',
new lang_string('auth_cas_language_key', 'auth_cas'),
new lang_string('auth_cas_language', 'auth_cas'), '', $CASLANGUAGES));
// Proxy.
$yesno = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_cas/proxycas',
new lang_string('auth_cas_proxycas_key', 'auth_cas'),
new lang_string('auth_cas_proxycas', 'auth_cas'), 0 , $yesno));
// Logout option.
$settings->add(new admin_setting_configselect('auth_cas/logoutcas',
new lang_string('auth_cas_logoutcas_key', 'auth_cas'),
new lang_string('auth_cas_logoutcas', 'auth_cas'), 0 , $yesno));
// Multi-auth.
$settings->add(new admin_setting_configselect('auth_cas/multiauth',
new lang_string('auth_cas_multiauth_key', 'auth_cas'),
new lang_string('auth_cas_multiauth', 'auth_cas'), 0 , $yesno));
// Server validation.
$settings->add(new admin_setting_configselect('auth_cas/certificate_check',
new lang_string('auth_cas_certificate_check_key', 'auth_cas'),
new lang_string('auth_cas_certificate_check', 'auth_cas'), 0 , $yesno));
// Certificate path.
$settings->add(new admin_setting_configfile('auth_cas/certificate_path',
get_string('auth_cas_certificate_path_key', 'auth_cas'),
get_string('auth_cas_certificate_path', 'auth_cas'), ''));
// CURL SSL version.
$sslversions = array();
$sslversions[''] = get_string('auth_cas_curl_ssl_version_default', 'auth_cas');
if (defined('CURL_SSLVERSION_TLSv1')) {
$sslversions[CURL_SSLVERSION_TLSv1] = get_string('auth_cas_curl_ssl_version_TLSv1x', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_0')) {
$sslversions[CURL_SSLVERSION_TLSv1_0] = get_string('auth_cas_curl_ssl_version_TLSv10', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_1')) {
$sslversions[CURL_SSLVERSION_TLSv1_1] = get_string('auth_cas_curl_ssl_version_TLSv11', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_2')) {
$sslversions[CURL_SSLVERSION_TLSv1_2] = get_string('auth_cas_curl_ssl_version_TLSv12', 'auth_cas');
}
if (defined('CURL_SSLVERSION_SSLv2')) {
$sslversions[CURL_SSLVERSION_SSLv2] = get_string('auth_cas_curl_ssl_version_SSLv2', 'auth_cas');
}
if (defined('CURL_SSLVERSION_SSLv3')) {
$sslversions[CURL_SSLVERSION_SSLv3] = get_string('auth_cas_curl_ssl_version_SSLv3', 'auth_cas');
}
$settings->add(new admin_setting_configselect('auth_cas/curl_ssl_version',
new lang_string('auth_cas_curl_ssl_version_key', 'auth_cas'),
new lang_string('auth_cas_curl_ssl_version', 'auth_cas'), '' , $sslversions));
// Alt Logout URL.
$settings->add(new admin_setting_configtext('auth_cas/logout_return_url',
get_string('auth_cas_logout_return_url_key', 'auth_cas'),
get_string('auth_cas_logout_return_url', 'auth_cas'), '', PARAM_URL));
// LDAP server settings.
$settings->add(new admin_setting_heading('auth_cas/ldapserversettings',
new lang_string('auth_ldap_server_settings', 'auth_ldap'), ''));
// Host.
$settings->add(new admin_setting_configtext('auth_cas/host_url',
get_string('auth_ldap_host_url_key', 'auth_ldap'),
get_string('auth_ldap_host_url', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Version.
$versions = array();
$versions[2] = '2';
$versions[3] = '3';
$settings->add(new admin_setting_configselect('auth_cas/ldap_version',
new lang_string('auth_ldap_version_key', 'auth_ldap'),
new lang_string('auth_ldap_version', 'auth_ldap'), 3, $versions));
// Start TLS.
$settings->add(new admin_setting_configselect('auth_cas/start_tls',
new lang_string('start_tls_key', 'auth_ldap'),
new lang_string('start_tls', 'auth_ldap'), 0 , $yesno));
// Encoding.
$settings->add(new admin_setting_configtext('auth_cas/ldapencoding',
get_string('auth_ldap_ldap_encoding_key', 'auth_ldap'),
get_string('auth_ldap_ldap_encoding', 'auth_ldap'), 'utf-8', PARAM_RAW_TRIMMED));
// Page Size. (Hide if not available).
$settings->add(new admin_setting_configtext('auth_cas/pagesize',
get_string('pagesize_key', 'auth_ldap'),
get_string('pagesize', 'auth_ldap'), '250', PARAM_INT));
// Bind settings.
$settings->add(new admin_setting_heading('auth_cas/ldapbindsettings',
new lang_string('auth_ldap_bind_settings', 'auth_ldap'), ''));
// User ID.
$settings->add(new admin_setting_configtext('auth_cas/bind_dn',
get_string('auth_ldap_bind_dn_key', 'auth_ldap'),
get_string('auth_ldap_bind_dn', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Password.
$settings->add(new admin_setting_configpasswordunmask('auth_cas/bind_pw',
get_string('auth_ldap_bind_pw_key', 'auth_ldap'),
get_string('auth_ldap_bind_pw', 'auth_ldap'), ''));
// User Lookup settings.
$settings->add(new admin_setting_heading('auth_cas/ldapuserlookup',
new lang_string('auth_ldap_user_settings', 'auth_ldap'), ''));
// User Type.
$settings->add(new admin_setting_configselect('auth_cas/user_type',
new lang_string('auth_ldap_user_type_key', 'auth_ldap'),
new lang_string('auth_ldap_user_type', 'auth_ldap'), 'default', ldap_supported_usertypes()));
// Contexts.
$settings->add(new auth_ldap_admin_setting_special_contexts_configtext('auth_cas/contexts',
get_string('auth_ldap_contexts_key', 'auth_ldap'),
get_string('auth_ldap_contexts', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Search subcontexts.
$settings->add(new admin_setting_configselect('auth_cas/search_sub',
new lang_string('auth_ldap_search_sub_key', 'auth_ldap'),
new lang_string('auth_ldap_search_sub', 'auth_ldap'), 0 , $yesno));
// Dereference aliases.
$optderef = array();
$optderef[LDAP_DEREF_NEVER] = get_string('no');
$optderef[LDAP_DEREF_ALWAYS] = get_string('yes');
$settings->add(new admin_setting_configselect('auth_cas/opt_deref',
new lang_string('auth_ldap_opt_deref_key', 'auth_ldap'),
new lang_string('auth_ldap_opt_deref', 'auth_ldap'), LDAP_DEREF_NEVER , $optderef));
// User attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_cas/user_attribute',
get_string('auth_ldap_user_attribute_key', 'auth_ldap'),
get_string('auth_ldap_user_attribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_cas/memberattribute',
get_string('auth_ldap_memberattribute_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
$settings->add(new admin_setting_configtext('auth_cas/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
// Object class.
$settings->add(new admin_setting_configtext('auth_cas/objectclass',
get_string('auth_ldap_objectclass_key', 'auth_ldap'),
get_string('auth_ldap_objectclass', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Course Creators Header.
$settings->add(new admin_setting_heading('auth_cas/coursecreators',
new lang_string('coursecreators'), ''));
// Course creators attribute field mapping.
$settings->add(new admin_setting_configtext('auth_cas/attrcreators',
get_string('auth_ldap_attrcreators_key', 'auth_ldap'),
get_string('auth_ldap_attrcreators', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Course creator group field mapping.
$settings->add(new admin_setting_configtext('auth_cas/groupecreators',
get_string('auth_ldap_groupecreators_key', 'auth_ldap'),
get_string('auth_ldap_groupecreators', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// User Account Sync.
$settings->add(new admin_setting_heading('auth_cas/syncusers',
new lang_string('auth_sync_script', 'auth'), ''));
// Remove external user.
$deleteopt = array();
$deleteopt[AUTH_REMOVEUSER_KEEP] = get_string('auth_remove_keep', 'auth');
$deleteopt[AUTH_REMOVEUSER_SUSPEND] = get_string('auth_remove_suspend', 'auth');
$deleteopt[AUTH_REMOVEUSER_FULLDELETE] = get_string('auth_remove_delete', 'auth');
$settings->add(new admin_setting_configselect('auth_cas/removeuser',
new lang_string('auth_remove_user_key', 'auth'),
new lang_string('auth_remove_user', 'auth'), AUTH_REMOVEUSER_KEEP, $deleteopt));
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin($this->name);
$help = get_string('auth_ldapextrafields', 'auth_ldap');
$help .= get_string('auth_updatelocal_expl', 'auth');
$help .= get_string('auth_fieldlock_expl', 'auth');
$help .= get_string('auth_updateremote_expl', 'auth');
$help .= '<hr />';
$help .= get_string('auth_updateremote_ldap', 'auth');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields, $help, true, true,
$authplugin->get_custom_user_profile_fields());
}
This files describes API changes in /auth/cas/*,
information provided here is intended especially for developers.
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/cas' to 'auth_cas'.
......@@ -26,8 +26,8 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016120500; // The current plugin version (Date: YYYYMMDDXX)
$plugin->version = 2017020700; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2016112900; // Requires this Moodle version
$plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics)
$plugin->dependencies = array('auth_ldap' => 2016112900);
$plugin->dependencies = array('auth_ldap' => 2017020700);
......@@ -41,7 +41,7 @@ class auth_plugin_db extends auth_plugin_base {
require_once($CFG->libdir.'/adodb/adodb.inc.php');
$this->authtype = 'db';
$this->config = get_config('auth/db');
$this->config = get_config('auth_db');
if (empty($this->config->extencoding)) {
$this->config->extencoding = 'utf-8';
}
......@@ -661,21 +661,6 @@ class auth_plugin_db extends auth_plugin_base {
return true;
}
/**
* A chance to validate form data, and last chance to
* do stuff before it is inserted in config_plugin
*
* @param stfdClass $form
* @param array $err errors
* @return void
*/
function validate_form($form, &$err) {
if ($form->passtype === 'internal') {
$this->config->changepasswordurl = '';
set_config('changepasswordurl', '', 'auth/db');
}
}
function prevent_local_passwords() {
return !$this->is_internal();
}
......@@ -752,95 +737,6 @@ class auth_plugin_db extends auth_plugin_base {
return $this->is_internal();
}
/**
* Prints a form for configuring this authentication plugin.
*
* This function is called from admin/auth.php, and outputs a full page with
* a form for configuring this plugin.
*
* @param stdClass $config
* @param array $err errors
* @param array $user_fields
* @return void
*/
function config_form($config, $err, $user_fields) {
include 'config.html';
}
/**
* Processes and stores configuration data for this authentication plugin.
*
* @param srdClass $config
* @return bool always true or exception
*/
function process_config($config) {
// set to defaults if undefined
if (!isset($config->host)) {
$config->host = 'localhost';
}
if (!isset($config->type)) {
$config->type = 'mysql';
}