From 478e5a59ec4210451046aec2012e5a20c6b70c5e Mon Sep 17 00:00:00 2001 From: Ahmad Gneady Date: Sat, 3 Jul 2021 02:06:52 +0200 Subject: [PATCH] As generated by AppGini 6.0. admin/incFunctions.php: add `getUserData()` and `setUserData()` to get/set user-specific data stored into `membership_users.data` Add admin/pageSQL.php to allow admin to easily query the database (and store queries for later reference). UI/UX enhancements for members list page under admin area. Fix stored XSS in `invoice_items_autofill.php` --- app/admin/ajax-saved-sql.php | 26 +++ app/admin/ajax-sql.php | 34 ++++ app/admin/getUsers.php | 2 +- app/admin/incFunctions.php | 39 +++++ app/admin/incHeader.php | 1 + app/admin/pageDeleteMember.php | 14 +- app/admin/pageDeleteRecord.php | 4 +- app/admin/pageSQL.js | 277 +++++++++++++++++++++++++++++++ app/admin/pageSQL.php | 124 ++++++++++++++ app/admin/pageServerStatus.php | 4 +- app/admin/pageViewMembers.php | 224 +++++++++++++++++-------- app/ajax_combo.php | 2 +- app/clients_autofill.php | 2 +- app/clients_dml.php | 2 +- app/clients_view.php | 2 +- app/defaultLang.php | 13 ++ app/dynamic.css | 2 +- app/footer.php | 2 +- app/incCommon.php | 12 +- app/invoice_items_autofill.php | 4 +- app/invoice_items_dml.php | 2 +- app/invoice_items_view.php | 2 +- app/invoices_autofill.php | 14 +- app/invoices_dml.php | 2 +- app/invoices_view.php | 2 +- app/item_prices_autofill.php | 2 +- app/item_prices_dml.php | 2 +- app/item_prices_view.php | 2 +- app/items_autofill.php | 2 +- app/items_dml.php | 2 +- app/items_view.php | 2 +- app/language.php | 93 ++++++----- app/lib.php | 2 +- app/login.php | 9 +- app/membership_passwordReset.php | 15 +- app/parent-children.php | 2 +- online-invoicing-system.axp | 2 +- 37 files changed, 775 insertions(+), 172 deletions(-) create mode 100644 app/admin/ajax-saved-sql.php create mode 100644 app/admin/ajax-sql.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..e36577f 100644 --- a/app/admin/getUsers.php +++ b/app/admin/getUsers.php @@ -1,5 +1,5 @@ @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 1c3d45a..d645aba 100644 --- a/app/admin/incHeader.php +++ b/app/admin/incHeader.php @@ -208,6 +208,7 @@ function hideDialogs() {
  • +
  • diff --git a/app/admin/pageDeleteMember.php b/app/admin/pageDeleteMember.php index e1b1a46..b6a75f0 100644 --- a/app/admin/pageDeleteMember.php +++ b/app/admin/pageDeleteMember.php @@ -1,19 +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 45ea255..4cc5e81 100644 --- a/app/admin/pageDeleteRecord.php +++ b/app/admin/pageDeleteRecord.php @@ -1,12 +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/pageSQL.js b/app/admin/pageSQL.js new file mode 100644 index 0000000..aeb5af9 --- /dev/null +++ b/app/admin/pageSQL.js @@ -0,0 +1,277 @@ +$j(function() { + var runningAjaxRequest = null; + var cache = {}; + var storedQueries = []; // [{name, query}, ..] + + var getResults = function(options) { + options = options || {}; + + if(runningAjaxRequest !== null) { + runningAjaxRequest.abort(); + runningAjaxRequest = null; + } + + var sql = $j('#sql').val(); + if(!validSql(sql)) { + if(typeof options.complete == 'function') options.complete(); + return noResults(); + } + + if(cache[sql] !== undefined && $j('#useCache').prop('checked')) { + if(typeof options.complete == 'function') options.complete(); + return showResults(cache[sql]); + } + + runningAjaxRequest = $j.ajax({ + url: 'ajax-sql.php', + data: { sql: sql, csrf_token: $j('#csrf_token').val() }, + beforeSend: function() { + $j('#sql-error').addClass('hidden'); + if(!$j('#auto-execute').hasClass('active')) noResults(); + $j('#results-loading').removeClass('hidden'); + }, + error: function(xhr) { + if(xhr.status == 403) { + // need new csrf token + $j('#csrf-expired').removeClass('hidden'); + $j('#no-sql-results').addClass('hidden'); + return; + } + + noResults(); + }, + success: function(resp) { + cache[sql] = resp; + showResults(resp); + }, + complete: function() { + runningAjaxRequest = null; + $j('#results-loading').addClass('hidden'); + if(typeof options.complete == 'function') + options.complete(); + } + }) + } + + var showResults = function(resp) { + resetResults(); + if( + typeof(resp) != 'object' || + resp.titles === undefined || + resp.data === undefined || + !resp.data.length + ) return noResults(resp); + + var thead = $j('#sql-results > 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)); +?> + + + + + +
    + +
    +
    + + +
    +
    +
    +
    +
    + + + + + + +
    +
    + +
    +
    + + +
    + +
    + + + + +
    +
    + + +
    + +
    +
    + +
      +
      +
      + +
      + +
      +
      +
      +
      + + + +
      + + +
      + + + + + + + + + + + +
      + + + + - - - - - - - - - + $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/ajax_combo.php b/app/ajax_combo.php index 287071b..3cc7665 100644 --- a/app/ajax_combo.php +++ b/app/ajax_combo.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 c1a4231..8c25e9f 100644 --- a/app/dynamic.css +++ b/app/dynamic.css @@ -137,7 +137,7 @@ img[src="blank.gif"] { max-height: 10px !important; } .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/footer.php b/app/footer.php index f866811..30641fa 100644 --- a/app/footer.php +++ b/app/footer.php @@ -9,7 +9,7 @@
      diff --git a/app/incCommon.php b/app/incCommon.php index 8c42b81..3e6e8ff 100644 --- a/app/incCommon.php +++ b/app/incCommon.php @@ -1128,17 +1128,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/invoice_items_autofill.php b/app/invoice_items_autofill.php index c086d75..d85b7e9 100644 --- a/app/invoice_items_autofill.php +++ b/app/invoice_items_autofill.php @@ -1,5 +1,5 @@ - $j('#current_price').html(' '); + $j('#current_price').html(' '); - $j('#client_contact').html(' '); - $j('#client_address').html(' '); - $j('#client_phone').html(' '); - $j('#client_email').html(' '); - $j('#client_website').html(' '); - $j('#client_comments').html(' '); + $j('#client_contact').html(' '); + $j('#client_address').html(' '); + $j('#client_phone').html(' '); + $j('#client_email').html(' '); + $j('#client_website').html(' '); + $j('#client_comments').html(' '); ). - -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 4a01dd8..b5db3b6 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 57d1f2b..7cff2ba 100644 --- a/app/membership_passwordReset.php +++ b/app/membership_passwordReset.php @@ -1,7 +1,6 @@ - +online_inovicing_systemlocalhostUTF-8False12FalseEurope/LondonFalseFalseTrueFalsebootstrap.cssFalseTrue0245.972021-06-14 16:19:49C:\xampp\htdocs\open-source-apps\online-invoicing-system\app0False1004False{"events":{"unpaid-invoice":{"type":"unpaid-invoice","color":"danger","textColor":"danger","table":"invoices","customWhere":"`invoices`.`status` = 'Unpaid'","title":"{5}<br>Invoice# {2}<br>{3}","allDay":true,"startDateField":"date_due","startTimeField":"","endDateField":"","endTimeField":""}},"calendars":{"unpaid-invoices":{"id":"unpaid-invoices","title":"Unpaid invoices","initial-view":"dayGridMonth","initial-date":"[last-month]","events":["unpaid-invoice"],"locale":"","groups":["Admins"],"links-home":"1","links-navmenu":"1"}}}3:4:2:12:14:9001:[{"report_hash":"pprh7b8b2dazav20cln9","title":"Client sales over time","table":"invoices","table_index":0,"label":"client","caption1":"Client","caption2":"Sum of Invoices","group_function":"sum","group_function_field":"total","group_array":[],"look_up_table":"clients","look_up_value":"name","label_field_index":"5","date_field":"date_due","date_field_index":"4","report_header_url":"","report_footer_url":"","data_table_section":1,"barchart_section":0,"piechart_section":0,"override_permissions":0,"custom_where":"","date_separator":"\/"}][{"label":"Mark as paid","icon":"ok","field":"status","value":"fixedValue","fixedValue":"Paid","confirmation":1,"groups":[],"hash":"1nvkk0q0ckqc7b8migay"},{"label":"Mark as cancelled","icon":"remove","field":"status","value":"fixedValue","fixedValue":"Cancelled","confirmation":1,"groups":[],"hash":"xe0xlisfn56ps9sp3p76"}]invoicesFalseFalseTrueFalseFalseTrueFalseTrueTrueTrueTrueTrueTrueFalseTrueTrue15010invoice_items;TrueFalseFalse2descattributes_display.png0True0TruehorizontalFalseFalse25%id400TrueFalseFalseTrueFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse0False00FalseFalseright0000Falsecode15200FalseFalseFalseFalseTrueFalseFalseFalse60False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseFalse10False00FalseFalseleft0000Falsestatus15200FalseFalseTrueFalseFalseFalseFalseFalse70False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse21FalseFalseFalseFalse12False00FalseFalseleft0000Falsedate_due900FalseFalseFalseFalseFalseFalseFalseFalse100False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseFalse1False00FalseFalseleft0000Falseclient400FalseFalseFalseFalseFalseTrueFalseFalse250False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsenameidclientsTrueTrueFalse01FalseFalseFalseFalse2False00FalseFalseleft0000Falseclient_contact400FalseFalseFalseFalseFalseTrueFalseFalse200False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsecontactidclientsTrueFalseTrue01FalseFalseFalseFalse3False00FalseFalseleft0000Falseclient_address400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseaddressidclientsTrueFalseTrue01TrueFalseFalseFalse4False00FalseFalseleft0000Falseclient_phone400FalseFalseFalseFalseFalseTrueFalseFalse100False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsephoneidclientsTrueFalseTrue01FalseFalseFalseFalse5False00FalseFalseleft0000Falseclient_email400FalseFalseFalseFalseFalseTrueFalseFalse50False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseemailidclientsTrueFalseTrue01TrueFalseFalseFalse6False00FalseFalseleft0000Falseclient_website400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsewebsiteidclientsTrueFalseTrue01TrueFalseFalseFalse8False00FalseFalseleft0000Falseclient_comments400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsecommentsidclientsTrueFalseTrue01TrueFalseFalseFalse7False00FalseFalseleft0000Falsesubtotal892FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseTrue14False00FalseFalseright0000Trueonline_inovicing_systemlocalhostUTF-8False12FalseEurope/LondonFalseFalseTrueFalsebootstrap.cssFalseTrue0246.02021-07-03 2:02:58C:\xampp\htdocs\open-source-apps\online-invoicing-system\app0False1004FalseTrue{"events":{"unpaid-invoice":{"type":"unpaid-invoice","color":"danger","textColor":"danger","table":"invoices","customWhere":"`invoices`.`status` = 'Unpaid'","title":"{5}<br>Invoice# {2}<br>{3}","allDay":true,"startDateField":"date_due","startTimeField":"","endDateField":"","endTimeField":""}},"calendars":{"unpaid-invoices":{"id":"unpaid-invoices","title":"Unpaid invoices","initial-view":"dayGridMonth","initial-date":"[last-month]","events":["unpaid-invoice"],"locale":"","groups":["Admins"],"links-home":"1","links-navmenu":"1"}}}
      3:4:2:12:14:9001:[{"report_hash":"pprh7b8b2dazav20cln9","title":"Client sales over time","table":"invoices","table_index":0,"label":"client","caption1":"Client","caption2":"Sum of Invoices","group_function":"sum","group_function_field":"total","group_array":[],"look_up_table":"clients","look_up_value":"name","label_field_index":"5","date_field":"date_due","date_field_index":"4","report_header_url":"","report_footer_url":"","data_table_section":1,"barchart_section":0,"piechart_section":0,"override_permissions":0,"custom_where":"","date_separator":"\/"}][{"label":"Mark as paid","icon":"ok","field":"status","value":"fixedValue","fixedValue":"Paid","confirmation":1,"groups":[],"hash":"1nvkk0q0ckqc7b8migay"},{"label":"Mark as cancelled","icon":"remove","field":"status","value":"fixedValue","fixedValue":"Cancelled","confirmation":1,"groups":[],"hash":"xe0xlisfn56ps9sp3p76"}]invoicesFalseFalseTrueFalseFalseTrueFalseTrueTrueTrueTrueTrueTrueFalseTrueTrue15010invoice_items;TrueFalseFalse2descattributes_display.png0True0TruehorizontalFalseFalse25%id400TrueFalseFalseTrueFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse0False00FalseFalseright0000Falsecode15200FalseFalseFalseFalseTrueFalseFalseFalse60False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseFalse10False00FalseFalseleft0000Falsestatus15200FalseFalseTrueFalseFalseFalseFalseFalse70False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse21FalseFalseFalseFalse12False00FalseFalseleft0000Falsedate_due900FalseFalseFalseFalseFalseFalseFalseFalse100False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseFalse1False00FalseFalseleft0000Falseclient400FalseFalseFalseFalseFalseTrueFalseFalse250False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsenameidclientsTrueTrueFalse01FalseFalseFalseFalse2False00FalseFalseleft0000Falseclient_contact400FalseFalseFalseFalseFalseTrueFalseFalse200False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsecontactidclientsTrueFalseTrue01FalseFalseFalseFalse3False00FalseFalseleft0000Falseclient_address400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseaddressidclientsTrueFalseTrue01TrueFalseFalseFalse4False00FalseFalseleft0000Falseclient_phone400FalseFalseFalseFalseFalseTrueFalseFalse100False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsephoneidclientsTrueFalseTrue01FalseFalseFalseFalse5False00FalseFalseleft0000Falseclient_email400FalseFalseFalseFalseFalseTrueFalseFalse50False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseemailidclientsTrueFalseTrue01TrueFalseFalseFalse6False00FalseFalseleft0000Falseclient_website400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsewebsiteidclientsTrueFalseTrue01TrueFalseFalseFalse8False00FalseFalseleft0000Falseclient_comments400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsecommentsidclientsTrueFalseTrue01TrueFalseFalseFalse7False00FalseFalseleft0000Falsesubtotal892FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseTrue14False00FalseFalseright0000Truediscount842FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse13False00FalseFalseright0000Falsetax892FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse15False00FalseFalseright0000Falsetotal892FalseFalseFalseFalseFalseFalseTrueFalse70False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseTrue9False00FalseFalseright0000True