From 2dc485ed006650c2c0d06467cb9e26f88a782481 Mon Sep 17 00:00:00 2001 From: Ahmad Gneady Date: Sat, 3 Jul 2021 23:57:41 +0200 Subject: [PATCH] Add admin SQL queries page to allow admin to easily query the database and store queries for later reference. Improved UI/UX of members list page in admin area. Fix various low severity stored and reflected xss vulnerabilities. Fix CSRF issues in various admin pages. Code refactoring in various files. --- app/admin/ajax-saved-sql.php | 26 ++ app/admin/ajax-sql.php | 34 +++ app/admin/getUsers.php | 3 +- app/admin/incFunctions.php | 41 ++- app/admin/incHeader.php | 1 + app/admin/pageDeleteGroup.php | 2 + app/admin/pageDeleteMember.php | 16 +- app/admin/pageDeleteRecord.php | 6 +- app/admin/pageDeleteRecord.php.bak | 24 ++ app/admin/pageEditMember.php | 2 +- app/admin/pageEditMemberPermissions.php | 2 +- app/admin/pageMail.php | 2 +- app/admin/pageQueryLogs.php | 177 +++++++++++ app/admin/pageSQL.js | 277 ++++++++++++++++++ app/admin/pageSQL.php | 124 ++++++++ app/admin/pageServerStatus.php | 4 +- app/admin/pageTransferOwnership.php | 2 +- app/admin/pageViewGroups.php | 4 +- app/admin/pageViewMembers.php | 232 ++++++++++----- app/admin/pageViewRecords.php | 2 +- app/ajax_admin_tools.php | 2 +- app/ajax_combo.php | 2 +- app/applicants_and_tenants_autofill.php | 2 +- app/applicants_and_tenants_dml.php | 4 +- app/applicants_and_tenants_view.php | 2 +- app/applications_leases_autofill.php | 2 +- app/applications_leases_dml.php | 4 +- app/applications_leases_view.php | 2 +- app/defaultLang.php | 13 + app/dynamic.css | 2 +- ...employment_and_income_history_autofill.php | 2 +- app/employment_and_income_history_dml.php | 4 +- app/employment_and_income_history_view.php | 2 +- app/footer.php | 2 +- app/incCommon.php | 12 +- app/language.php | 93 +++--- app/lib.php | 2 +- app/login.php | 9 +- app/membership_passwordReset.php | 17 +- app/membership_profile.php | 4 +- app/parent-children.php | 2 +- app/properties_autofill.php | 2 +- app/properties_dml.php | 2 +- app/properties_view.php | 2 +- app/property_photos_autofill.php | 2 +- app/property_photos_dml.php | 4 +- app/property_photos_view.php | 2 +- app/references_autofill.php | 2 +- app/references_dml.php | 2 +- app/references_view.php | 2 +- app/rental_owners_autofill.php | 2 +- app/rental_owners_dml.php | 4 +- app/rental_owners_view.php | 2 +- app/residence_and_rental_history_autofill.php | 2 +- app/residence_and_rental_history_dml.php | 4 +- app/residence_and_rental_history_view.php | 2 +- .../applicants_and_tenants-ajax-cache.php | 4 +- .../applications_leases-ajax-cache.php | 4 +- ...ployment_and_income_history-ajax-cache.php | 4 +- app/templates/properties-ajax-cache.php | 4 +- app/templates/property_photos-ajax-cache.php | 4 +- app/templates/references-ajax-cache.php | 4 +- app/templates/rental_owners-ajax-cache.php | 4 +- ...esidence_and_rental_history-ajax-cache.php | 4 +- app/templates/unit_photos-ajax-cache.php | 4 +- app/templates/units-ajax-cache.php | 4 +- app/unit_photos_autofill.php | 2 +- app/unit_photos_dml.php | 4 +- app/unit_photos_view.php | 2 +- app/units_autofill.php | 12 +- app/units_dml.php | 4 +- app/units_view.php | 2 +- orpm.axp | 2 +- 73 files changed, 1040 insertions(+), 229 deletions(-) create mode 100644 app/admin/ajax-saved-sql.php create mode 100644 app/admin/ajax-sql.php create mode 100644 app/admin/pageDeleteRecord.php.bak create mode 100644 app/admin/pageQueryLogs.php create mode 100644 app/admin/pageSQL.js create mode 100644 app/admin/pageSQL.php diff --git a/app/admin/ajax-saved-sql.php b/app/admin/ajax-saved-sql.php new file mode 100644 index 0000000..6794a3e --- /dev/null +++ b/app/admin/ajax-saved-sql.php @@ -0,0 +1,26 @@ + [], 'data' => [], 'error' => '']; + $eo = ['silentErrors' => true]; + + $res = sql($sql, $eo); + if(!$res) + $resp['error'] = $eo['error']; + else while($row = db_fetch_assoc($res)) { + if(!count($resp['titles'])) + $resp['titles'] = array_keys($row); + + $resp['data'][] = array_map('htmlspecialchars', array_values($row)); + } + + @header('Content-type: application/json'); + echo json_encode($resp, JSON_PARTIAL_OUTPUT_ON_ERROR); diff --git a/app/admin/getUsers.php b/app/admin/getUsers.php index 498a952..13508cd 100644 --- a/app/admin/getUsers.php +++ b/app/admin/getUsers.php @@ -1,5 +1,5 @@ to_utf8($row[0]), 'text' => to_utf8("{$row[1]}/{$row[0]}")); } diff --git a/app/admin/incFunctions.php b/app/admin/incFunctions.php index 651596f..3a47a22 100644 --- a/app/admin/incFunctions.php +++ b/app/admin/incFunctions.php @@ -1216,7 +1216,9 @@ function update_membership_users() { `comments` TEXT, `pass_reset_key` VARCHAR(100), `pass_reset_expiry` INT UNSIGNED, + `flags` TEXT, `allowCSVImport` TINYINT NOT NULL DEFAULT '0', + `data` LONGTEXT, PRIMARY KEY (`memberID`), INDEX `groupID` (`groupID`) ) CHARSET " . mysql_charset, @@ -1229,6 +1231,7 @@ function update_membership_users() { sql("ALTER TABLE `{$tn}` ADD INDEX `groupID` (`groupID`)", $eo); sql("ALTER TABLE `{$tn}` ADD COLUMN `flags` TEXT", $eo); sql("ALTER TABLE `{$tn}` ADD COLUMN `allowCSVImport` TINYINT NOT NULL DEFAULT '0'", $eo); + sql("ALTER TABLE `{$tn}` ADD COLUMN `data` LONGTEXT", $eo); } ######################################################################## function update_membership_userrecords() { @@ -1582,7 +1585,7 @@ function html_attr($str) { function html_attr_tags_ok($str) { // use this instead of html_attr() if you don't want html tags to be escaped $new_str = html_attr($str); - return str_replace(array('<', '>'), array('<', '>'), $new_str); + return str_replace(['<', '>'], ['<', '>'], $new_str); } ######################################################### class Notification{ @@ -2582,3 +2585,39 @@ function assocArrFilter($arr, $func) { return $filtered; } + ######################################################### + function setUserData($key, $value = null) { + $data = []; + + $user = makeSafe(getMemberInfo()['username']); + if(!$user) return false; + + $dataJson = sqlValue("SELECT `data` FROM `membership_users` WHERE `memberID`='$user'"); + if($dataJson) { + $data = @json_decode($dataJson, true); + if(!$data) $data = []; + } + + $data[$key] = $value; + + return update( + 'membership_users', + ['data' => @json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR)], + ['memberID' => $user] + ); + } + ######################################################### + function getUserData($key) { + $user = makeSafe(getMemberInfo()['username']); + if(!$user) return null; + + $dataJson = sqlValue("SELECT `data` FROM `membership_users` WHERE `memberID`='$user'"); + if(!$dataJson) return null; + + $data = @json_decode($dataJson, true); + if(!$data) return null; + + if(!isset($data[$key])) return null; + + return $data[$key]; + } diff --git a/app/admin/incHeader.php b/app/admin/incHeader.php index e1f5eb7..b5f1a58 100644 --- a/app/admin/incHeader.php +++ b/app/admin/incHeader.php @@ -213,6 +213,7 @@ function hideDialogs() {
  • +
  • diff --git a/app/admin/pageDeleteGroup.php b/app/admin/pageDeleteGroup.php index 237bb99..d463586 100644 --- a/app/admin/pageDeleteGroup.php +++ b/app/admin/pageDeleteGroup.php @@ -5,6 +5,8 @@ // validate input $groupID = intval($_GET['groupID']); + if(!csrf_token(true)) die($Translation['csrf token expired or invalid']); + // make sure group has no members if(sqlValue("select count(1) from membership_users where groupID='{$groupID}'")) { errorMsg($Translation["can not delete group remove members"]); diff --git a/app/admin/pageDeleteMember.php b/app/admin/pageDeleteMember.php index 92b2707..b6a75f0 100644 --- a/app/admin/pageDeleteMember.php +++ b/app/admin/pageDeleteMember.php @@ -1,17 +1,17 @@ true]; + sql("DELETE FROM `membership_users` WHERE LCASE(`memberID`)='$memberID'", $eo); + sql("UPDATE `membership_userrecords` SET `memberID`='' WHERE LCASE(`memberID`)='$memberID'", $eo); if($_SERVER['HTTP_REFERER']) { redirect($_SERVER['HTTP_REFERER'], TRUE); } else { - redirect("admin/pageViewMembers.php"); + redirect('admin/pageViewMembers.php'); } - -?> \ No newline at end of file diff --git a/app/admin/pageDeleteRecord.php b/app/admin/pageDeleteRecord.php index 00ad850..4cc5e81 100644 --- a/app/admin/pageDeleteRecord.php +++ b/app/admin/pageDeleteRecord.php @@ -1,10 +1,12 @@ true]; $res = sql("SELECT `tableName`, `pkValue` FROM `membership_userrecords` WHERE `recID`='{$recID}'", $eo); if($row = db_fetch_row($res)) { sql("DELETE FROM `membership_userrecords` WHERE `recID`='{$recID}'", $eo); diff --git a/app/admin/pageDeleteRecord.php.bak b/app/admin/pageDeleteRecord.php.bak new file mode 100644 index 0000000..32cc796 --- /dev/null +++ b/app/admin/pageDeleteRecord.php.bak @@ -0,0 +1,24 @@ + true]; + $res = sql("SELECT `tableName`, `pkValue` FROM `membership_userrecords` WHERE `recID`='{$recID}'", $eo); + if($row = db_fetch_row($res)) { + sql("DELETE FROM `membership_userrecords` WHERE `recID`='{$recID}'", $eo); + if($pkName = getPKFieldName($row[0])) { + sql("DELETE FROM `{$row[0]}` WHERE `{$pkName}`='" . makeSafe($row[1]) . "'", $eo); + } + } + + if($_SERVER['HTTP_REFERER']) { + redirect($_SERVER['HTTP_REFERER'], TRUE); + exit; + } + + redirect('admin/pageViewRecords.php'); diff --git a/app/admin/pageEditMember.php b/app/admin/pageEditMember.php index c29d45f..eea3951 100644 --- a/app/admin/pageEditMember.php +++ b/app/admin/pageEditMember.php @@ -139,7 +139,7 @@ } elseif($_GET['groupID'] != '') { // show the form for adding a new member, and pre-select the provided group $groupID = intval($_GET['groupID']); - $group_name = sqlValue("select name from membership_groups where groupID='$groupID'"); + $group_name = strip_tags(sqlValue("select name from membership_groups where groupID='$groupID'")); if($group_name) $addend = " to '{$group_name}'"; } diff --git a/app/admin/pageEditMemberPermissions.php b/app/admin/pageEditMemberPermissions.php index f8b0125..316796c 100644 --- a/app/admin/pageEditMemberPermissions.php +++ b/app/admin/pageEditMemberPermissions.php @@ -19,7 +19,7 @@ $anonGroupID = sqlValue("select groupID from membership_groups where lcase(name)='" . strtolower(makeSafe($anonymousGroup)) . "'"); $adminGroupID = sqlValue("select groupID from membership_groups where name='Admins'"); $groupID = sqlValue("select groupID from membership_users where lcase(memberID)='{$memberID->sql}'"); - $group = sqlValue("select name from membership_groups where groupID='{$groupID}'"); + $group = strip_tags(sqlValue("select name from membership_groups where groupID='{$groupID}'")); if($groupID == $anonGroupID || $memberID->raw == $anonymousMember || !$groupID || $groupID == $adminGroupID || $memberID->raw == $adminConfig['adminUsername']) { // error in request. redirect to members page. redirect('admin/pageViewMembers.php'); diff --git a/app/admin/pageMail.php b/app/admin/pageMail.php index 0d58210..12b7d00 100644 --- a/app/admin/pageMail.php +++ b/app/admin/pageMail.php @@ -139,7 +139,7 @@

    - +

    diff --git a/app/admin/pageQueryLogs.php b/app/admin/pageQueryLogs.php new file mode 100644 index 0000000..5907f11 --- /dev/null +++ b/app/admin/pageQueryLogs.php @@ -0,0 +1,177 @@ + 'COALESCE(`duration`, 0) > 0', + 'error' => 'CHAR_LENGTH(COALESCE(`error`, \'\')) > 0', + ]; + + $type = $_REQUEST['type']; + if(!in_array($type, array_keys($queryTypes))) $type = 'slow'; + + $page = intval($_REQUEST['page']); + if($page < 1) $page = 1; + + // Starting record from $page (page is 1-based, while firstRecord is 0-based) + $recordsPerPage = config('adminConfig')['recordsPerPage']; + // $firstRecord is calculated below after retrieving $lastPage + + // set up log table if necessary + if(!createQueryLogTable()) + dieErrorPage($Translation['Query log table does not exist']); + + // only keep queries that are at most 2 months old + $eo = ['silentErrors' => true]; + sql("DELETE FROM `appgini_query_log` WHERE `datetime` < DATE_SUB(NOW(), INTERVAL 2 MONTH)", $eo); + + // build the WHERE clause + $where = "WHERE {$queryTypes[$type]}"; + /* future work: allow filtering by user, date period, ... etc, and allow sorting */ + + // how many records and pages in total do we have? + $totalRecords = sqlValue("SELECT COUNT(1) FROM `appgini_query_log` $where"); + $lastPage = ceil($totalRecords / $recordsPerPage); + + // adjust $page and $firstRecord if needed + $page = max(1, min($page, $lastPage)); + $firstRecord = ($page - 1) * $recordsPerPage; + + // get app uri to strip from stored uri + $appURI = config('appURI'); + + // retrieve requested logs + $records = []; + $res = sql("SELECT * FROM `appgini_query_log` + $where + ORDER BY `datetime` DESC + LIMIT $firstRecord, $recordsPerPage", $eo); + while($row = db_fetch_assoc($res)) { + if($appURI) $row['uri'] = ltrim(str_replace($appURI, '', $row['uri']), '/'); + $records[] = $row; + } + + include(__DIR__ . "/incHeader.php"); +?> + + + + +
    + + +
    + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + : + +
    +
    +
    +
    + +
    +
    + ', ''], + [$totalRecords, $page, $lastPage], + $Translation['total # queries'] . ' ' . $Translation['page x of y'] + ); ?> +
    +
    + +
    +
    +
    +
    + + + + + + table > thead > tr'), + tbody = $j('#sql-results > table > tbody'), + tr = null; + + for(var i = 0; i < resp.titles.length; i++) { + var th = $j('' + resp.titles[i] + ''); + + // max length of corresponding data + var maxDataLength = 0; + for(var ri = 0; ri < resp.data.length; ri++) + if(resp.data[ri][i].length > maxDataLength) + maxDataLength = resp.data[ri][i].length; + + // if data is too wide, offer option to expand/collapse column + if(maxDataLength > 3 * resp.titles[i].length) { + var width = resp.titles[i].length * 10 + 20; + $j('') + .on('click', function() { + var me = $j(this), + index = me.parents('th').index(); + + if(me.hasClass('rotate180')) { // collapsed, requesting expansion + me.removeClass('rotate180'); + $j('tbody > tr > td:nth-child(' + (index + 1) + ')') + .css({ + 'max-width': 'unset', + overflow: 'auto' + }) + } else { // expanded (default), requesting collapse + me.addClass('rotate180'); + $j('tbody > tr > td:nth-child(' + (index + 1) + ')') + .css({ + 'max-width': width + 'px', + overflow: 'hidden' + }) + } + }) + .appendTo(th); + } + + th.appendTo(thead); + } + + for(var ri = 0; ri < resp.data.length; ri++) { + tr = $j(''); + $j('' + (ri + 1) + '').appendTo(tr); + + for(i = 0; i < resp.data[ri].length; i++) + $j('' + resp.data[ri][i] + '').appendTo(tr); + + tr.appendTo(tbody); + } + + $j('#sql-results').removeClass('hidden'); + $j('#no-sql-results, #sql-error').addClass('hidden'); + + $j('#sql-results-truncated').toggleClass('hidden', resp.data.length != 1000); + } + + var noResults = function(resp) { + $j('#sql-results').addClass('hidden'); + $j('#no-sql-results').removeClass('hidden'); + $j('#results-loading').addClass('hidden'); + + var hasError = (resp !== undefined && resp.error); + $j('#sql-error').toggleClass('hidden', !hasError).html(hasError ? resp.error : ''); + } + + var validSql = function(sql) { + if(sql === undefined) sql = $j('#sql').val(); + $j('#sql-begins-not-with-select').toggleClass('hidden', /^\s*SELECT\s+/i.test(sql)); + return /^\s*SELECT\s+.*?\s+FROM\s+\S+/i.test(sql); + } + + var resetResults = function() { + var table = $j('#sql-results > table'); + table.find('th:not(.row-counter)').remove(); + table.find('tbody > tr').remove(); + } + + var listStoredQueries = function() { + var list = $j('#manage-queries-dialog .list-group'); + list.empty(); + + for(var i = 0; i < storedQueries.length; i++) { + // add item to list of bookmarks + $j('
  • ' + + '' + + '' + + '
  • ').appendTo(list); + } + } + + var saveStoredQueries = function() { + $j.ajax({ + url: 'ajax-saved-sql.php', + data: { + queries: JSON.stringify(storedQueries), + csrf_token: $j('#csrf_token').val() + } + }); + } + + var cleanName = function(name) { return name.trim().replace(/[^\w \-_$+,;.]/g, ''); } + + $j('#execute').on('click', getResults); + + var autoExecTimeout = null; + $j('#sql').on('keyup', function() { + if(!validSql()) return; + if(!$j('#auto-execute').hasClass('active')) return; + + // auto retrieve results if no typing for 2 seconds + clearTimeout(autoExecTimeout) + autoExecTimeout = setTimeout(function() { getResults(); }, 2000); + }); + + $j('#useCache').on('click', function() { + if(!$j(this).prop('checked')) + getResults(); + }) + + $j('#reset').on('click', function() { + $j('#sql').val('').focus(); + resetResults(); + noResults(); + }) + + // lock/unlock #execute button + $j('#auto-execute').on('click', function() { + var enable = $j(this).hasClass('active'); + $j(this).toggleClass('active', !enable); + $j('#execute').prop('disabled', !enable); + }) + + // retrieve stored queries + $j.ajax({ + url: 'ajax-saved-sql.php', + data: { csrf_token: $j('#csrf_token').val() }, + success: function(resp) { + storedQueries = resp.length ? JSON.parse(resp) : []; + if(storedQueries.length === undefined) storedQueries = []; + + // validate and clean storedQueries + storedQueries = storedQueries.filter(function(i) { + return typeof i.name == 'string' && typeof i.query == 'string'; + }).map(function(i) { + return { name: cleanName(i.name), query: i.query }; + }); + + listStoredQueries(); + } + }); + + // insertables + $j('.insertable').on('click', function() { + var sql = $j('#sql'), + insertable = $j(this).text(), + selStart = sql[0].selectionStart, + selEnd = sql[0].selectionEnd, + txt = sql.val(); + + sql.val(txt.substr(0, selStart) + insertable + txt.substr(selEnd)); + sql[0].selectionStart = selStart; + sql[0].selectionEnd = selStart + insertable.length; + sql.focus(); + }) + + // handle bookmarked queries + // auto-focus query name on showing bookmark dialog + $j('#manage-queries-dialog').on('shown.bs.collapse', function() { $j('#save-query-as').val('').focus(); }); + + $j('#save-query').on('click', function() { + var name = cleanName($j('#save-query-as').val()); + if(!name.length) return $j('#save-query-as').val('').focus(); + + // if name exists, update + var nameExists = false; + storedQueries.map(function(i) { + if(i.name != name) return; + i.query = $j('#sql').val(); + nameExists = true; + }); + + if(!nameExists) storedQueries.push({ name: name, query: $j('#sql').val() }); + listStoredQueries(); + saveStoredQueries(); + + $j('#save-query-as').val(''); + $j('#save-query-as-dialog').collapse('hide'); + }) + + $j('#manage-queries-dialog') + .on('click', '.delete-bookmark', function() { + var id = $j(this).data('id'); + storedQueries.splice(id, 1); + listStoredQueries(); + saveStoredQueries(); + }) + .on('click', '.load-bookmark', function() { + var id = $j(this).data('id'); + $j('#sql').val(storedQueries[id].query); + getResults({ + complete: function() { + $j('#manage-queries-dialog').collapse('hide'); + } + }); + }) +}) diff --git a/app/admin/pageSQL.php b/app/admin/pageSQL.php new file mode 100644 index 0000000..2fb6857 --- /dev/null +++ b/app/admin/pageSQL.php @@ -0,0 +1,124 @@ +
    $i"; + }, $items)); +?> + + + + + +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + + + +
    +
    + +
    +
    + + +
    + +
    + + + + +
    +
    + + +
    + +
    +
    + +
      +
      +
      + +
      + +
      +
      +
      +
      + + + +
      + + +
      + + + + + + + + + + + : - + diff --git a/app/admin/pageViewGroups.php b/app/admin/pageViewGroups.php index 600faa8..a989abf 100644 --- a/app/admin/pageViewGroups.php +++ b/app/admin/pageViewGroups.php @@ -74,13 +74,13 @@ $groupMembersCount = sqlValue("select count(1) from membership_users where groupID='$row[0]'"); ?> - + - diff --git a/app/admin/pageViewMembers.php b/app/admin/pageViewMembers.php index cc829ce..9be3f1d 100644 --- a/app/admin/pageViewMembers.php +++ b/app/admin/pageViewMembers.php @@ -4,6 +4,37 @@ $GLOBALS['page_title'] = $Translation['members']; include("{$currDir}/incHeader.php"); + // fields of memebers view + $df = makeSafe($adminConfig['MySQLDateFormat'], false); + + $mviewFields = [ + 'LCASE(m.memberID)', + 'g.name', + "DATE_FORMAT(m.signupDate, '$df')", + 'm.email', + 'm.custom1', 'm.custom2', 'm.custom3', 'm.custom4', + 'm.isBanned', + 'm.isApproved', + ]; + + // process sorting + $sort = 2; // default sorting by signupDate + $sortDir = 'DESC'; // default sorting direction + + if(isset($_GET['sort'])) { + // make sure requested sort is a valid index of mviewFields. fallback to default sort index + $rSort = intval($_GET['sort']); + if(!isset($mviewFields[$rSort])) $rSort = $sort; + + // make sure requested sortDir is valid. fallback to default sortDir + $rSortDir = $sortDir; + if(!empty($_GET['sortDir']) && in_array(strtoupper($rSortDir), ['DESC', 'ASC'])) + $rSortDir = strtoupper($_GET['sortDir']); + + $sort = $rSort; + $sortDir = $rSortDir; + } + // process search if($_GET['searchMembers'] != "") { $searchSQL = makeSafe($_GET['searchMembers']); @@ -85,70 +116,108 @@ ?> +
      + + + + - - - - - - - - - + $title) { + $sortIcon = ''; + $nextSortDir = 'asc'; + if($sort == $i) { + $sortIcon = ''; + if($sortDir == 'ASC') $nextSortDir = 'desc'; + } + printf( + '', + $i, $nextSortDir, $title, $sortIcon + ); + } + ?> - + - + - - - + + + + - -
      - - + + -
      - ',''); - $searchValue = ''; - $arrFields = array(0, 1, 2, 3, 4, 5, 6, 7, 8); - $arrFieldCaptions = array($Translation['all fields'], $Translation['username'], $Translation["group"], $Translation["email"], $adminConfig['custom1'], $adminConfig['custom2'], $adminConfig['custom3'], $adminConfig['custom4'], $Translation["comments"]); - $htmlSelect = htmlSelect('searchField', $arrFields, $arrFieldCaptions, $searchField); - $replaceValues = array($searchValue, $htmlSelect); - echo str_replace($originalValues, $replaceValues, $Translation['search members']); - ?> -
      +
      + ',''); + $searchValue = ''; + $arrFields = array(0, 1, 2, 3, 4, 5, 6, 7, 8); + $arrFieldCaptions = array($Translation['all fields'], $Translation['username'], $Translation["group"], $Translation["email"], $adminConfig['custom1'], $adminConfig['custom2'], $adminConfig['custom3'], $adminConfig['custom4'], $Translation["comments"]); + $htmlSelect = htmlSelect('searchField', $arrFields, $arrFieldCaptions, $searchField); + $replaceValues = array($searchValue, $htmlSelect); + echo str_replace($originalValues, $replaceValues, $Translation['search members']); + ?> +
      -
      - - -
      +
      + + +
      -
      - - -
      +
      + + +
      -
      - - -
      - +
      + + +
      %s%s 
      + + " + href="pageMail.php?memberID="> + + - + - + - + " title="">" title="">">">">"> - "> - - - - - "> - + ">
      + + 1 ? $page - 1 : false; + $nextPage = $page < ceil($numMembers / $adminConfig['membersPerPage']) ? $page + 1 : false; + ?>
      - + + + - + + +
      - + +
      -
      -
      -
      -
      -
      -
      +
      +
      +
      +
      +
      +
      + diff --git a/app/admin/pageViewRecords.php b/app/admin/pageViewRecords.php index a125a0e..05f37e5 100644 --- a/app/admin/pageViewRecords.php +++ b/app/admin/pageViewRecords.php @@ -120,7 +120,7 @@ - + diff --git a/app/ajax_admin_tools.php b/app/ajax_admin_tools.php index 7e1e3fd..e9de177 100644 --- a/app/ajax_admin_tools.php +++ b/app/ajax_admin_tools.php @@ -209,7 +209,7 @@ public function get_record_info() { $rec_info['dateAdded'] = date($admin_config['PHPDateTimeFormat'], $rec_info['dateAdded']); $rec_info['dateUpdated'] = date($admin_config['PHPDateTimeFormat'], $rec_info['dateUpdated']); - return @json_encode($rec_info); + return @json_encode(array_map('strip_tags', $rec_info)); } /** diff --git a/app/ajax_combo.php b/app/ajax_combo.php index ed31b91..36f5d9d 100644 --- a/app/ajax_combo.php +++ b/app/ajax_combo.php @@ -1,5 +1,5 @@ ', html_attr($row['status']), $templateCode); $templateCode = str_replace('<%%URLVALUE(status)%%>', urlencode($urow['status']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '
      ' . $row['notes'] . '
      ', $templateCode); } diff --git a/app/applicants_and_tenants_view.php b/app/applicants_and_tenants_view.php index cd02f9f..498405d 100644 --- a/app/applicants_and_tenants_view.php +++ b/app/applicants_and_tenants_view.php @@ -1,5 +1,5 @@ ', urlencode($urow['co_signer_details']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '
      ' . $row['notes'] . '
      ', $templateCode); } diff --git a/app/applications_leases_view.php b/app/applications_leases_view.php index c400c7a..d388ced 100644 --- a/app/applications_leases_view.php +++ b/app/applications_leases_view.php @@ -1,5 +1,5 @@ 'Query', 'page x of y' => 'Page of ', 'total # queries' => 'Total # queries.', + + // Added in 6.0 + 'Interactive SQL queries tool' => 'Interactive SQL queries tool', + 'Enter SQL query' => 'Enter SQL query', + 'Query must start with select' => 'Query must start with %s', + 'Display results' => 'Display results', + 'Update results as you type' => 'Update results as you type', + 'Use cache' => 'Use cache', + 'results truncated' => 'Results below might have been truncated to a limit of %s records for performance purposes.', + 'Results will be displayed here' => 'Results will be displayed here.', + 'Bookmark this query' => 'Bookmark this query', + 'Query name' => 'Query name', + 'Manage bookmarked queries' => 'Manage bookmarked queries', ]; diff --git a/app/dynamic.css b/app/dynamic.css index 6104c03..40b6407 100644 --- a/app/dynamic.css +++ b/app/dynamic.css @@ -210,7 +210,7 @@ td.units-description { white-space: normal !important; max-width: 80px !importan .tv-tools .btn { width: 5em; } /* compact theme styles */ -.container.theme-compact, .container-fluid.theme-compact { font-size: 0.857em; } +.container.theme-compact, .container-fluid.theme-compact { line-height: 1.6; font-size: 0.857em; } .theme-compact .btn { font-size: 12px; diff --git a/app/employment_and_income_history_autofill.php b/app/employment_and_income_history_autofill.php index 31bb858..496e3b6 100644 --- a/app/employment_and_income_history_autofill.php +++ b/app/employment_and_income_history_autofill.php @@ -1,5 +1,5 @@ ', html_attr($row['occupation']), $templateCode); $templateCode = str_replace('<%%URLVALUE(occupation)%%>', urlencode($urow['occupation']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '
      ' . $row['notes'] . '
      ', $templateCode); } diff --git a/app/employment_and_income_history_view.php b/app/employment_and_income_history_view.php index e764728..70a1e9e 100644 --- a/app/employment_and_income_history_view.php +++ b/app/employment_and_income_history_view.php @@ -1,5 +1,5 @@
      diff --git a/app/incCommon.php b/app/incCommon.php index e914d48..0e16555 100644 --- a/app/incCommon.php +++ b/app/incCommon.php @@ -1222,17 +1222,7 @@ function PrepareUploadedFile($FieldName, $MaxSize, $FileTypes = 'jpg|jpeg|gif|pn $dir = getUploadDir($dir); /* get php.ini upload_max_filesize in bytes */ - $php_upload_size_limit = trim(ini_get('upload_max_filesize')); - $last = strtolower($php_upload_size_limit[strlen($php_upload_size_limit) - 1]); - switch($last) { - case 'g': - $php_upload_size_limit *= 1024; - case 'm': - $php_upload_size_limit *= 1024; - case 'k': - $php_upload_size_limit *= 1024; - } - + $php_upload_size_limit = toBytes(ini_get('upload_max_filesize')); $MaxSize = min($MaxSize, $php_upload_size_limit); if($f['size'] > $MaxSize || $f['error']) { diff --git a/app/language.php b/app/language.php index 0e781a8..7e43a88 100644 --- a/app/language.php +++ b/app/language.php @@ -1,43 +1,43 @@ -). - -Do NOT translate the strings between square brackets ([]) -Also, leave the text between < and > untranslated. -And also, leave the special string %s as-is, untranslated. - -===================================================== -PLEASE NOTE: -============ -When a new version of AppGini is released, new strings -might be added to the "defaultLang.php" file. To translate -them, simply copy them to this file ("language.php") and -translate them here. Do NOT translate them directly in -the "defaultLang.php" file. -===================================================== - -To avoid any error messages, please make sure to: - -1. Add a comma at the end of each line if it's not already there. -2. Leave the last line in this file like this: -]; - -*/ - -$Translation = [ - 'language' => 'english', +). + +Do NOT translate the strings between square brackets ([]) +Also, leave the text between < and > untranslated. +And also, leave the special string %s as-is, untranslated. + +===================================================== +PLEASE NOTE: +============ +When a new version of AppGini is released, new strings +might be added to the "defaultLang.php" file. To translate +them, simply copy them to this file ("language.php") and +translate them here. Do NOT translate them directly in +the "defaultLang.php" file. +===================================================== + +To avoid any error messages, please make sure to: + +1. Add a comma at the end of each line if it's not already there. +2. Leave the last line in this file like this: +]; + +*/ + +$Translation = [ + 'language' => 'english', 'membership management' => 'Membership Management', 'password mismatch' => "Password doesn't match.", 'error' => 'Error', @@ -809,4 +809,17 @@ 'query' => 'Query', 'page x of y' => 'Page of ', 'total # queries' => 'Total # queries.', + + // Added in 6.0 + 'Interactive SQL queries tool' => 'Interactive SQL queries tool', + 'Enter SQL query' => 'Enter SQL query', + 'Query must start with select' => 'Query must start with %s', + 'Display results' => 'Display results', + 'Update results as you type' => 'Update results as you type', + 'Use cache' => 'Use cache', + 'results truncated' => 'Results below might have been truncated to a limit of %s records for performance purposes.', + 'Results will be displayed here' => 'Results will be displayed here.', + 'Bookmark this query' => 'Bookmark this query', + 'Query name' => 'Query name', + 'Manage bookmarked queries' => 'Manage bookmarked queries', ]; \ No newline at end of file diff --git a/app/lib.php b/app/lib.php index e029d00..2c25ba3 100644 --- a/app/lib.php +++ b/app/lib.php @@ -1,5 +1,5 @@

      - - - -
      + + +
      + +
      diff --git a/app/membership_passwordReset.php b/app/membership_passwordReset.php index 0d56f0d..7cff2ba 100644 --- a/app/membership_passwordReset.php +++ b/app/membership_passwordReset.php @@ -1,7 +1,6 @@
      - +
      @@ -95,7 +94,7 @@ - +
      - +
      - +
      diff --git a/app/parent-children.php b/app/parent-children.php index 88c57e8..35f49a7 100644 --- a/app/parent-children.php +++ b/app/parent-children.php @@ -1,5 +1,5 @@ ', html_attr($row['photo']), $templateCode); $templateCode = str_replace('<%%URLVALUE(photo)%%>', urlencode($urow['photo']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(description)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(description)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(description)%%>', '
      ' . $row['description'] . '
      ', $templateCode); } diff --git a/app/property_photos_view.php b/app/property_photos_view.php index b70d2bd..00d51fc 100644 --- a/app/property_photos_view.php +++ b/app/property_photos_view.php @@ -1,5 +1,5 @@ ', html_attr($row['zip']), $templateCode); $templateCode = str_replace('<%%URLVALUE(zip)%%>', urlencode($urow['zip']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(comments)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(comments)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(comments)%%>', '
      ' . $row['comments'] . '
      ', $templateCode); } diff --git a/app/rental_owners_view.php b/app/rental_owners_view.php index aae97cd..6b66a0b 100644 --- a/app/rental_owners_view.php +++ b/app/rental_owners_view.php @@ -1,5 +1,5 @@ ', html_attr($row['reason_for_leaving']), $templateCode); $templateCode = str_replace('<%%URLVALUE(reason_for_leaving)%%>', urlencode($urow['reason_for_leaving']), $templateCode); if($AllowUpdate || $AllowInsert) { - $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); + $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '', $templateCode); } else { $templateCode = str_replace('<%%HTMLAREA(notes)%%>', '
      ' . $row['notes'] . '
      ', $templateCode); } diff --git a/app/residence_and_rental_history_view.php b/app/residence_and_rental_history_view.php index a6803d9..35fcd68 100644 --- a/app/residence_and_rental_history_view.php +++ b/app/residence_and_rental_history_view.php @@ -1,5 +1,5 @@