예제 #1
0
/**
 * delete list
 */
function delete_test_list()
{
    $list_table = new ListTable(REGRESSION_TEST_LIST_TITLE);
    # check if we have to cleanup
    if (REGRESSION_TEST_CLEANUP == FALSE) {
        return TRUE;
    }
    if ($list_table->drop() == TRUE) {
        return TRUE;
    }
    return FALSE;
}
예제 #2
0
/**
 * set list filter string (function is called when user hits filter button)
 * this function is registered in xajax
 * @param string $list_title title of list
 * @param string $filter_str filter string that user has set
 * @return xajaxResponse every xajax registered function needs to return this object
 */
function action_set_list_filter($list_title, $filter_str)
{
    global $logging;
    global $user;
    global $list_state;
    global $list_table_configuration;
    global $user_start_time_array;
    $logging->info("USER_ACTION " . __METHOD__ . " (user="******", list_title={$list_title}, filter_str={$filter_str})");
    # store start time
    $user_start_time_array[__METHOD__] = microtime(TRUE);
    # create necessary objects
    $result = new Result();
    $response = new xajaxResponse();
    $html_database_table = new HtmlDatabaseTable($list_table_configuration);
    # check if filter_str is well formed
    if (str_is_well_formed("filter_str", $filter_str) == FALSE_RETURN_STRING) {
        set_error_message("filter_form", "below", "ERROR_NOT_WELL_FORMED_STRING", "", "", $response);
        return $response;
    }
    # create list table object
    $list_table = new ListTable($list_title);
    if ($list_table->get_is_valid() == FALSE) {
        $logging->warn("create list object returns false");
        $error_message_str = $list_table->get_error_message_str();
        $error_log_str = $list_table->get_error_log_str();
        $error_str = $list_table->get_error_str();
        set_error_message("filter_form", "below", $error_message_str, $error_log_str, $error_str, $response);
        return $response;
    }
    # set filter value
    $user->get_list_state($list_table->get_table_name());
    $list_state->set_filter_str($filter_str);
    $list_state->set_filter_str_sql("");
    $user->set_list_state();
    # set content
    $html_database_table->get_content($list_table, $list_title, "", DATABASETABLE_UNKWOWN_PAGE, $result);
    $response->custom_response->assign_with_effect(LIST_CSS_NAME_PREFIX . "content_pane", $result->get_result_str());
    # check post conditions
    if (check_postconditions($result, $response) == FALSE) {
        return $response;
    }
    # log total time for this function
    $logging->info(get_function_time_str(__METHOD__));
    return $response;
}
/**
 * generate html for one attachment and for the add attachment button
 * @param string $list_title title of current list
 * @param int $attachment_id id of the attachment to generate
 * @param string $attachment_specifications specifications of attachment
 * return string resulting html
 */
function get_list_record_attachment($list_title, $attachment_id, $attachment_specifications)
{
    global $logging;
    $logging->trace("getting list_record_attachment (attachment_specs={$attachment_specifications}");
    $attachment_array = explode("|", $attachment_specifications);
    $tmp_file_name = $attachment_array[0];
    $file_type = $attachment_array[1];
    $file_size = $attachment_array[2];
    $file_name = $attachment_array[3];
    $logging->info("id={$attachment_id}, tmp_file_name={$tmp_file_name}, type={$file_type}, size={$file_size}, name={$file_name}");
    $db_field_name = ListTable::_get_db_field_name(DB_ATTACHMENTS_NAME);
    # set name and id of input
    $input_name = $db_field_name . GENERAL_SEPARATOR . FIELD_TYPE_DEFINITION_ATTACHMENTS . GENERAL_SEPARATOR . $attachment_id;
    if ($attachment_id == 0) {
        $html_str = "                                        <div id=\"upload_line\" class=\"upload_line\">\n";
        $html_str .= "                                            <span class=\"invisible_collapsed\" id=\"upload_attachment_id\">1</span>";
        $html_str .= "<input class=\"invisible_collapsed\" name=\"{$input_name}\" id=\"{$input_name}\" value=\"" . LISTTABLEATTACHMENT_EMPTY_ATTACHMENT . "|-|-|-\">";
        $html_str .= "<span id=\"upload_animation\"></span>";
        $html_str .= "<span id=\"button_upload\" class=\"icon icon_add\">" . translate("BUTTON_SELECT_UPLOAD_FILE") . "</a></span>\n";
        $html_str .= "                                        </div>\n";
    } else {
        $html_str = "                                        <div id=\"attachment_{$attachment_id}\" class=\"attachment_line\">\n";
        $html_str .= "                                            <span id=\"attachment_name_{$attachment_id}\">";
        $html_str .= "<input class=\"invisible_collapsed\" name=\"{$input_name}\" id=\"{$input_name}\" value=\"{$attachment_specifications}\">";
        # existing attachments get a clickable link
        if ($tmp_file_name == LISTTABLEATTACHMENT_EXISTING_ATTACHMENT) {
            $html_str .= get_href(get_onclick(ACTION_DOWNLOAD_ATTACHMENT, HTML_NO_PERMISSION_CHECK, "", "", "('{$list_title}', '{$attachment_id}')"), str_replace(" ", "&nbsp;", $file_name), "icon_attachment") . "&nbsp;&nbsp;";
        } else {
            $html_str .= str_replace(" ", "&nbsp;", $file_name) . "&nbsp;&nbsp;";
        }
        $html_str .= get_href(get_onclick(ACTION_DELETE_ATTACHMENT, HTML_NO_PERMISSION_CHECK, "", "", "('{$attachment_id}', \$('#{$input_name}').val())"), translate("BUTTON_DELETE"), "icon_delete") . "</span>\n";
        $html_str .= "                                        </div>\n";
    }
    $logging->trace("got list_record_attachment");
    return $html_str;
}
echo "set active records and archived records for all lists<br>";
$query = "SELECT " . LISTTABLEDESCRIPTION_TITLE_FIELD_NAME . " FROM " . LISTTABLEDESCRIPTION_TABLE_NAME;
#echo "query=".$query."<br>";
$query_result = $database->query($query);
if ($query_result != FALSE) {
    $all_lists = array();
    while ($row = $database->fetch($query_result)) {
        #echo "&nbsp;&nbsp;&nbsp;found list <strong>".$row[0]."</strong><br>";
        array_push($all_lists, $row[0]);
    }
} else {
    fatal("could not find any lists");
}
foreach ($all_lists as $one_list) {
    echo "&nbsp;&nbsp;&nbsp;update list <strong>{$one_list}</strong><br>";
    $table_name = ListTable::_convert_list_name_to_table_name($one_list);
    # select active records
    $query = "SELECT COUNT(" . DB_ID_FIELD_NAME . ") FROM {$table_name} WHERE " . DB_TS_ARCHIVED_FIELD_NAME . "='" . DB_NULL_DATETIME . "'";
    #echo "query=".$query."<br>";
    $query_result = $database->query($query);
    if ($query_result == FALSE) {
        fatal("could not find active records of list: {$one_list}");
    }
    $result = $database->fetch($query_result);
    $active_records = $result[0];
    # select archived records
    $query = "SELECT COUNT(" . DB_ID_FIELD_NAME . ") FROM {$table_name} WHERE " . DB_TS_ARCHIVED_FIELD_NAME . ">'" . DB_NULL_DATETIME . "'";
    #echo "query=".$query."<br>";
    $query_result = $database->query($query);
    if ($query_result == FALSE) {
        fatal("could not find archived records of list: {$one_list}");
echo "upgrading definitions of all lists<br>";
$query = "SELECT " . DB_ID_FIELD_NAME . ", " . LISTTABLEDESCRIPTION_TITLE_FIELD_NAME . ", " . LISTTABLEDESCRIPTION_DEFINITION_FIELD_NAME . " FROM " . LISTTABLEDESCRIPTION_TABLE_NAME;
$query_result = $database->query($query);
if ($query_result != FALSE) {
    $all_lists = array();
    while ($row = $database->fetch($query_result)) {
        #echo "&nbsp;&nbsp;&nbsp;found list <strong>".$row[0]."</strong><br>";
        array_push($all_lists, $row);
    }
} else {
    fatal("could not find any lists");
}
foreach ($all_lists as $one_list) {
    $list_id = $one_list[DB_ID_FIELD_NAME];
    $list_name = $one_list[LISTTABLEDESCRIPTION_TITLE_FIELD_NAME];
    $table_name = ListTable::_convert_list_name_to_table_name($list_name);
    $definition = $one_list[LISTTABLEDESCRIPTION_DEFINITION_FIELD_NAME];
    $definition_array = (array) $json->decode(html_entity_decode($definition, ENT_QUOTES));
    echo "&nbsp;&nbsp;&nbsp;update list <strong>{$list_name}</strong><br>";
    $field_names = array_keys($definition_array);
    echo "&nbsp;&nbsp;&nbsp;updating list definition<br>\n";
    $new_definition = array();
    foreach ($field_names as $field_name) {
        $field_definition = $definition_array[$field_name];
        $field_definition_str = htmlentities($json->encode($field_definition), ENT_QUOTES);
        $field_type = $field_definition[0];
        $field_options = $field_definition[1];
        #echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;before: $field_definition_str<br>";
        #echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;found field <strong>".$field_name."</strong> of field type ".$field_type." with options ".$field_options."<br>\n";
        if (count($field_definition) != 2) {
            fatal("definition of list has incorrect number of columns");
    while ($row = $database->fetch($query_result)) {
        #echo "&nbsp;&nbsp;&nbsp;found list <strong>".$row[0]."</strong><br>";
        array_push($all_lists, array($row[0], $row[1]));
    }
} else {
    fatal("could not find any lists");
}
foreach ($all_lists as $row) {
    $list_name = $row[0];
    $table_name = ListTable::_convert_list_name_to_table_name($list_name);
    $definition = (array) $json->decode(html_entity_decode(html_entity_decode($row[1], ENT_QUOTES), ENT_QUOTES));
    $db_field_names = array_keys($definition);
    foreach ($db_field_names as $db_field_name) {
        $field_definition = $definition[$db_field_name];
        $field_type = $field_definition[0];
        $field_name = ListTable::_get_field_name($db_field_name);
        if ($field_type == "LABEL_DEFINITION_DATE") {
            $new_field_type = FIELD_TYPE_DEFINITION_DATETIME;
        }
        $new_field_type = $field_type;
        $new_field_definition = array();
        array_push($new_field_definition, $new_field_type);
        array_push($new_field_definition, $field_definition[1]);
        $definition[$field_name] = $new_field_definition;
        # update auto created or auto update field
        if ($field_type == "LABEL_DEFINITION_DATE") {
            # update list table description
            echo "&nbsp;&nbsp;&nbsp;update field <strong>{$field_name}</strong> of list <strong>{$list_name}</strong><br>";
            $query = "ALTER TABLE {$table_name} MODIFY COLUMN {$db_field_name} " . DB_DATATYPE_DATETIME;
            echo $query . "<br>\n";
            $result = $database->query($query);
예제 #7
0
/**
 * check if given definition is correct
 * @todo remove (obsolete) key indicator from definition (requires an update script)
 * @param array $definition defintion of current list that is being build
 * @param $response xajaxResponse response object
 * @return array returns an empty array when given definition was not correct
 */
function check_definition($definition, $response)
{
    global $logging;
    $logging->trace("check definition");
    $definition_values = array_values($definition);
    $definition_keys = array_keys($definition);
    $new_definition = array();
    if (count($definition_values) / 4 < 2) {
        $logging->warn("list with only one field");
        set_error_message($definition_keys[2], "right", "ERROR_NOT_ENOUGH_FIELDS", "", "", $response);
        return array();
    }
    for ($position = 0; $position < count($definition_values) / 4; $position += 1) {
        $field_id = $definition_values[$position * 4];
        $field_type = $definition_values[$position * 4 + 1];
        $field_name = $definition_values[$position * 4 + 2];
        $field_options = $definition_values[$position * 4 + 3];
        $logging->debug("found field (id=\"" . $field_id . "\" name=" . $field_name . " type=" . $field_type . " options=" . $field_options . ")");
        # check if field name has been given
        if (strlen($field_name) == 0) {
            $logging->warn("no field name given");
            set_error_message($definition_keys[$position * 4 + 2], "right", "ERROR_NO_FIELD_NAME_GIVEN", "", "", $response);
            return array();
        }
        # check if field name is well formed
        if (str_is_well_formed("field", $field_name) == FALSE_RETURN_STRING) {
            set_error_message($definition_keys[$position * 4 + 2], "right", "ERROR_NOT_WELL_FORMED_STRING", "", "", $response);
            return array();
        }
        # check if field is of type FIELD_TYPE_DEFINITION_SELECTION
        if ($field_type == FIELD_TYPE_DEFINITION_SELECTION) {
            # check if options string has been given
            if (strlen($field_options) == 0) {
                $logging->warn("no options given");
                set_error_message($definition_keys[$position * 4 + 3], "right", "ERROR_NO_FIELD_OPTIONS_GIVEN", "", "", $response);
                return array();
            }
            # check if options string is well formed
            if (str_is_well_formed("field", $field_options, 1) == FALSE_RETURN_STRING) {
                set_error_message($definition_keys[$position * 4 + 3], "right", "ERROR_NOT_WELL_FORMED_SELECTION_STRING", "", "", $response);
                return array();
            }
        }
        # copy in field_visible_in_overview
        $new_field_name = ListTable::_get_db_field_name($field_name);
        $field_visible_in_overview = COLUMN_SHOW;
        if ($new_field_name == DB_ID_FIELD_NAME) {
            $field_visible_in_overview = $field_options;
            $field_options = "";
        }
        $new_definition[$field_id] = array(ListTable::_get_db_field_name($field_name), $field_type, $field_options, $field_visible_in_overview);
    }
    $logging->trace("checked definition");
    return $new_definition;
}
예제 #8
0
 * definition of all test definitions
 */
define("REGRESSION_TEST_USER_NAME", "tester");
define("REGRESSION_TEST_USER_PW", "sometestpassword");
define("REGRESSION_TEST_USER_NEW_PW", "someotherpassword");
define("REGRESSION_TEST_LIST_TITLE", "regression test");
define("REGRESSION_TEST_LIST_DESCRIPTION", "This is a list that has been created only for testing purposes");
define("REGRESSION_TEST_LIST_NEW_DESCRIPTION", "This is a list that has been created only for testing purposes [UPDATED BY REGRESSION TEST]");
define("REGRESSION_TEST_LIST_ID_FIELD", ListTable::_get_db_field_name("id"));
define("REGRESSION_TEST_LIST_NAME_FIELD", ListTable::_get_db_field_name("name"));
define("REGRESSION_TEST_LIST_DESCRIPTION_FIELD", ListTable::_get_db_field_name("description"));
define("REGRESSION_TEST_LIST_NOTES1_FIELD", ListTable::_get_db_field_name("first notes"));
define("REGRESSION_TEST_LIST_STATUS_FIELD", ListTable::_get_db_field_name("status"));
define("REGRESSION_TEST_LIST_NOTES2_FIELD", ListTable::_get_db_field_name("second notes"));
define("REGRESSION_TEST_LIST_CREATED_FIELD", ListTable::_get_db_field_name("created"));
define("REGRESSION_TEST_LIST_ATTACHMENTS_FIELD", ListTable::_get_db_field_name(DB_ATTACHMENTS_NAME));
/**
 * definition of all test functions (as needed by Html.RegressionTest.php)
 */
$regression_test_functions = array(array("create and modify user", "", "", ""), array("login as 'administrator'", "login_user_admin", "'administrator' logged in", "could not login administrator"), array("create a new test user for this test", "create_test_user", "test user created", "could not create new test user"), array("login as test user", "login_test_user", "test user logged in", "could not login test user"), array("login as 'administrator'", "login_user_admin", "'administrator' logged in", "could not login administrator"), array("change password of test user", "update_test_user", "password of test user updated", "could not change password of test user"), array("login as test user", "login_test_user_new_pw", "test user logged in", "could not login test user"), array("create and modify test list", "", "", ""), array("create a new test list", "create_test_list", "test list created", "could not create new test list"), array("open test list", "open_test_list", "test list opened", "could not open test list"), array("add 20 list items to test list", "add_list_items", "added 20 list items to test list", "could not add list items to test list"), array("update description of test list", "update_test_list", "updated description of test list", "could not update test list"), array("read 9th list item from test list", "read_list_item", "read 9th list item test list", "could not read list item from test list"), array("update the second note of the first notes column of the 10th list item from test list", "update_list_item", "updated 10th list item test list", "could not update list item from test list"), array("delete 20th list item from test list", "delete_list_item", "deleted 20th list item from test list", "could not delete list item from test list"), array("cleanup", "", "", ""), array("delete test list", "delete_test_list", "deleted test list", "could not delete test list"), array("delete test user", "delete_test_user", "deleted test user", "could not delete test user"));
/**
 * definition of new user 'regressiontester'
 */
$regression_tester_user_array = array(USER_NAME_FIELD_NAME => REGRESSION_TEST_USER_NAME, USER_PW_FIELD_NAME => REGRESSION_TEST_USER_PW, USER_LANG_FIELD_NAME => LANG_EN, USER_DATE_FORMAT_FIELD_NAME => DATE_FORMAT_EU, USER_DECIMAL_MARK_FIELD_NAME => DECIMAL_MARK_POINT, USER_LINES_PER_PAGE_FIELD_NAME => 12, USER_THEME_FIELD_NAME => THEME_BLUE, USER_CAN_CREATE_LIST_FIELD_NAME => 1, USER_CAN_CREATE_USER_FIELD_NAME => 0, USER_IS_ADMIN_FIELD_NAME => 0, USER_TIMES_LOGIN_FIELD_NAME => 0);
/**
 * definition of new list 'regression test list'
 */
$regression_test_list_definition = array(REGRESSION_TEST_LIST_ID_FIELD => array(FIELD_TYPE_DEFINITION_AUTO_NUMBER, "", COLUMN_SHOW), REGRESSION_TEST_LIST_NAME_FIELD => array(FIELD_TYPE_DEFINITION_TEXT_LINE, "", COLUMN_SHOW), REGRESSION_TEST_LIST_DESCRIPTION_FIELD => array(FIELD_TYPE_DEFINITION_TEXT_LINE, "", COLUMN_SHOW), REGRESSION_TEST_LIST_NOTES1_FIELD => array(FIELD_TYPE_DEFINITION_NOTES_FIELD, "", ""), REGRESSION_TEST_LIST_STATUS_FIELD => array(FIELD_TYPE_DEFINITION_SELECTION, "open|busy|pending|close", COLUMN_SHOW), REGRESSION_TEST_LIST_NOTES2_FIELD => array(FIELD_TYPE_DEFINITION_NOTES_FIELD, "", COLUMN_SHOW), REGRESSION_TEST_LIST_ATTACHMENTS_FIELD => array(FIELD_TYPE_DEFINITION_ATTACHMENTS, "", COLUMN_SHOW));
$regression_test_list_description_definition = array(LISTTABLEDESCRIPTION_TITLE_FIELD_NAME => REGRESSION_TEST_LIST_TITLE, LISTTABLEDESCRIPTION_DESCRIPTION_FIELD_NAME => REGRESSION_TEST_LIST_DESCRIPTION, LISTTABLEDESCRIPTION_DEFINITION_FIELD_NAME => $regression_test_list_definition, LISTTABLEDESCRIPTION_ACTIVE_RECORDS_FIELD_NAME => 0, LISTTABLEDESCRIPTION_ARCHIVED_RECORDS_FIELD_NAME => 0, LISTTABLEDESCRIPTION_CREATOR_FIELD_NAME => 0, LISTTABLEDESCRIPTION_MODIFIER_FIELD_NAME => 0);
$regression_test_new_list_definition = array(0 => array(REGRESSION_TEST_LIST_ID_FIELD, FIELD_TYPE_DEFINITION_AUTO_NUMBER, "", COLUMN_NO_SHOW), 6 => array(REGRESSION_TEST_LIST_CREATED_FIELD, FIELD_TYPE_DEFINITION_AUTO_CREATED, NAME_DATE_OPTION_DATE_NAME, COLUMN_SHOW), 1 => array(REGRESSION_TEST_LIST_NAME_FIELD, FIELD_TYPE_DEFINITION_TEXT_LINE, "", COLUMN_SHOW), 3 => array(REGRESSION_TEST_LIST_NOTES1_FIELD, FIELD_TYPE_DEFINITION_NOTES_FIELD, "", COLUMN_SHOW), 2 => array(REGRESSION_TEST_LIST_DESCRIPTION_FIELD, FIELD_TYPE_DEFINITION_TEXT_LINE, "", COLUMN_SHOW), 4 => array(REGRESSION_TEST_LIST_STATUS_FIELD, FIELD_TYPE_DEFINITION_SELECTION, "open|busy|pending|close", COLUMN_SHOW), 5 => array(REGRESSION_TEST_LIST_NOTES2_FIELD, FIELD_TYPE_DEFINITION_NOTES_FIELD, "", COLUMN_SHOW), 7 => array(REGRESSION_TEST_LIST_ATTACHMENTS_FIELD, FIELD_TYPE_DEFINITION_ATTACHMENTS, "", COLUMN_SHOW));
/**
예제 #9
0
/**
 * delete a list table
 * this function is registered in xajax
 * @param string $list_title title of list table
 * @param string $key_string comma separated name value pairs
 * @return xajaxResponse every xajax registered function needs to return this object
 */
function action_delete_portal_record($list_title)
{
    global $logging;
    global $user;
    global $list_table_description;
    global $portal_table_configuration;
    global $user_start_time_array;
    $logging->info("USER_ACTION " . __METHOD__ . " (user="******", list_title={$list_title})");
    # store start time
    $user_start_time_array[__METHOD__] = microtime(TRUE);
    # create necessary objects
    $result = new Result();
    $response = new xajaxResponse();
    $list_table = new ListTable($list_title);
    if ($list_table->get_is_valid() == FALSE) {
        $logging->warn("create list object returns false");
        $error_message_str = $list_table->get_error_message_str();
        $error_log_str = $list_table->get_error_log_str();
        $error_str = $list_table->get_error_str();
        set_error_message("tab_portal_id", "below", $error_message_str, $error_log_str, $error_str, $response);
        return $response;
    }
    $html_database_table = new HtmlDatabaseTable($portal_table_configuration);
    # display error when delete returns false
    if ($list_table->drop() == FALSE) {
        $logging->warn("drop list returns false");
        $error_message_str = $list_table->get_error_message_str();
        $error_log_str = $list_table->get_error_log_str();
        $error_str = $list_table->get_error_str();
        set_error_message("tab_portal_id", "below", $error_message_str, $error_log_str, $error_str, $response);
        return $response;
    }
    # set content
    $html_database_table->get_content($list_table_description, $list_title, "", DATABASETABLE_ALL_PAGES, $result);
    $response->custom_response->assign_with_effect(PORTAL_CSS_NAME_PREFIX . "content_pane", $result->get_result_str());
    # reset current list name only when active list has been removed
    if ($list_title == $user->get_current_list_name()) {
        $user->set_current_list_name("");
    }
    # set page navigation and login status to update old 'list' links
    $page_navigation_str = get_page_navigation(PAGE_TYPE_PORTAL);
    $response->assign("navigation_container", "innerHTML", $page_navigation_str);
    # check post conditions
    if (check_postconditions($result, $response) == FALSE) {
        return $response;
    }
    # log total time for this function
    $logging->info(get_function_time_str(__METHOD__));
    return $response;
}
    /**
     * Echoes plugin installation form.
     *
     * This method is the callback for the admin_menu method function.
     * This displays the admin page and form area where the user can select to install and activate the plugin.
     *
     * @return null Aborts early if we're processing a plugin installation action
     */
    public function install_plugins_page()
    {
        /** Store new instance of plugin table in object */
        $plugin_table = new ListTable();
        /** Return early if processing a plugin installation action */
        if (isset($_POST[sanitize_key('action')]) && 'tgmpa-bulk-install' == $_POST[sanitize_key('action')] && $plugin_table->process_bulk_actions() || $this->do_plugin_install()) {
            return;
        }
        ?>
        <div class="tgmpa wrap">

            <?php 
        screen_icon(apply_filters('tgmpa_default_screen_icon', 'themes'));
        ?>
            <h2><?php 
        echo esc_html(get_admin_page_title());
        ?>
</h2>
            <?php 
        $plugin_table->prepare_items();
        ?>

            <?php 
        if (isset($this->message)) {
            _e(wp_kses_post($this->message), self::TEXT_DOMAIN);
        }
        ?>

            <form id="tgmpa-plugins" action="" method="post">
                <input type="hidden" name="tgmpa-page" value="<?php 
        echo $this->menu;
        ?>
" />
                <?php 
        $plugin_table->display();
        ?>
            </form>

        </div>
        <?php 
    }