diff --git a/app/admin/getUsers.php b/app/admin/getUsers.php
index 5c08c45..498a952 100644
--- a/app/admin/getUsers.php
+++ b/app/admin/getUsers.php
@@ -1,5 +1,5 @@
escaped_str */
+ if(!strlen($string)) return '';
+
if(!db_link()) sql("SELECT 1+1", $eo);
// if this is a previously escaped string, return from cached
@@ -297,115 +299,191 @@ function checkPermissionVal($pvn) {
}
}
########################################################################
- if(!function_exists('sql')) {
- function sql($statment, &$o) {
+ function dieErrorPage($error) {
+ global $Translation;
- /*
- Supported options that can be passed in $o options array (as array keys):
- 'silentErrors': If true, errors will be returned in $o['error'] rather than displaying them on screen and exiting.
- */
+ $header = (defined('ADMIN_AREA') ? __DIR__ . '/incHeader.php' : __DIR__ . '/../header.php');
+ $footer = (defined('ADMIN_AREA') ? __DIR__ . '/incFooter.php' : __DIR__ . '/../footer.php');
- global $Translation;
- static $connected = false, $db_link;
+ ob_start();
- $dbServer = config('dbServer');
- $dbUsername = config('dbUsername');
- $dbPassword = config('dbPassword');
- $dbDatabase = config('dbDatabase');
+ @include_once($header);
+ echo Notification::placeholder();
+ echo Notification::show([
+ 'message' => $error,
+ 'class' => 'danger',
+ 'dismiss_seconds' => 7200
+ ]);
+ @include_once($footer);
- $admin_dir = dirname(__FILE__);
- $header = (defined('ADMIN_AREA') ? "{$admin_dir}/incHeader.php" : "{$admin_dir}/../header.php");
- $footer = (defined('ADMIN_AREA') ? "{$admin_dir}/incFooter.php" : "{$admin_dir}/../footer.php");
+ echo ob_get_clean();
+ exit;
+ }
+ ########################################################################
+ function openDBConnection(&$o) {
+ static $connected = false, $db_link;
- ob_start();
+ $dbServer = config('dbServer');
+ $dbUsername = config('dbUsername');
+ $dbPassword = config('dbPassword');
+ $dbDatabase = config('dbDatabase');
- if(!$connected) {
- /****** Connect to MySQL ******/
- if(!extension_loaded('mysql') && !extension_loaded('mysqli')) {
- $o['error'] = 'PHP is not configured to connect to MySQL on this machine. Please see this page for help on how to configure MySQL.';
- if($o['silentErrors']) return false;
-
- @include_once($header);
- echo Notification::placeholder();
- echo Notification::show(array(
- 'message' => $o['error'],
- 'class' => 'danger',
- 'dismiss_seconds' => 7200
- ));
- @include_once($footer);
- echo ob_get_clean();
- exit;
- }
-
- if(!($db_link = @db_connect($dbServer, $dbUsername, $dbPassword))) {
- $o['error'] = db_error($db_link, true);
- if($o['silentErrors']) return false;
-
- @include_once($header);
- echo Notification::placeholder();
- echo Notification::show(array(
- 'message' => $o['error'],
- 'class' => 'danger',
- 'dismiss_seconds' => 7200
- ));
- @include_once($footer);
- echo ob_get_clean();
- exit;
- }
-
- /****** Select DB ********/
- if(!db_select_db($dbDatabase, $db_link)) {
- $o['error'] = db_error($db_link);
- if($o['silentErrors']) return false;
-
- @include_once($header);
- echo Notification::placeholder();
- echo Notification::show(array(
- 'message' => $o['error'],
- 'class' => 'danger',
- 'dismiss_seconds' => 7200
- ));
- @include_once($footer);
- echo ob_get_clean();
- exit;
- }
-
- $connected = true;
- }
+ if($connected) return $db_link;
- if(!$result = @db_query($statment, $db_link)) {
- if(!stristr($statment, "show columns")) {
- // retrieve error codes
- $errorNum = db_errno($db_link);
- $errorMsg = htmlspecialchars(db_error($db_link));
-
- if(getLoggedAdmin()) $errorMsg .= "
{$Translation['query:']}\n" . htmlspecialchars($statment) . "
{$Translation['admin-only info']}
{$Translation['try rebuild fields']}
";
-
- if($o['silentErrors']) { $o['error'] = $errorMsg; return false; }
-
- @include_once($header);
- echo Notification::placeholder();
- echo Notification::show(array(
- 'message' => $errorMsg,
- 'class' => 'danger',
- 'dismiss_seconds' => 7200
- ));
- @include_once($footer);
- echo ob_get_clean();
- exit;
- }
- }
+ /****** Check that MySQL module is enabled ******/
+ if(!extension_loaded('mysql') && !extension_loaded('mysqli')) {
+ $o['error'] = 'PHP is not configured to connect to MySQL on this machine. Please see this page for help on how to configure MySQL.';
+ if($o['silentErrors']) return false;
- ob_end_clean();
- return $result;
+ dieErrorPage($o['error']);
+ }
+
+ /****** Connect to MySQL ******/
+ if(!($db_link = @db_connect($dbServer, $dbUsername, $dbPassword))) {
+ $o['error'] = db_error($db_link, true);
+ if($o['silentErrors']) return false;
+
+ dieErrorPage($o['error']);
+ }
+
+ /****** Select DB ********/
+ if(!db_select_db($dbDatabase, $db_link)) {
+ $o['error'] = db_error($db_link);
+ if($o['silentErrors']) return false;
+
+ dieErrorPage($o['error']);
}
+
+ $connected = true;
+ return $db_link;
+ }
+ ########################################################################
+ function sql($statement, &$o) {
+
+ /*
+ Supported options that can be passed in $o options array (as array keys):
+ 'silentErrors': If true, errors will be returned in $o['error'] rather than displaying them on screen and exiting.
+ 'noSlowQueryLog': don't log slow query if true
+ 'noErrorQueryLog': don't log error query if true
+ */
+
+ global $Translation;
+
+ $db_link = openDBConnection($o);
+
+ /*
+ if openDBConnection() fails, it would abort execution unless 'silentErrors' is true,
+ in which case, we should return false from sql() without further action since
+ $o['error'] would be already set by openDBConnection()
+ */
+ if(!$db_link) return false;
+
+ $t0 = microtime(true);
+
+ if(!$result = @db_query($statement, $db_link)) {
+ if(!stristr($statement, "show columns")) {
+ // retrieve error codes
+ $errorNum = db_errno($db_link);
+ $o['error'] = htmlspecialchars(db_error($db_link));
+
+ if(empty($o['noErrorQueryLog']))
+ logErrorQuery($statement, $o['error']);
+
+ if(getLoggedAdmin())
+ $o['error'] .= "{$Translation['query:']}\n" . htmlspecialchars($statement) . "
{$Translation['admin-only info']}
{$Translation['try rebuild fields']}
";
+
+ if($o['silentErrors']) return false;
+
+ dieErrorPage($o['error']);
+ }
+ }
+
+ /* log slow queries that take more than 1 sec */
+ $t1 = microtime(true);
+ if($t1 - $t0 > 1.0 && empty($o['noSlowQueryLog']))
+ logSlowQuery($statement, $t1 - $t0);
+
+ return $result;
+ }
+ ########################################################################
+ function logSlowQuery($statement, $duration) {
+ if(!createQueryLogTable()) return;
+
+ $o = [
+ 'silentErrors' => true,
+ 'noSlowQueryLog' => true,
+ 'noErrorQueryLog' => true
+ ];
+ $statement = makeSafe($statement);
+ $duration = floatval($duration);
+ $memberID = makeSafe(getLoggedMemberID());
+ $uri = makeSafe($_SERVER['REQUEST_URI']);
+
+ sql("INSERT INTO `appgini_query_log` SET
+ `statement`='$statement',
+ `duration`=$duration,
+ `memberID`='$memberID',
+ `uri`='$uri'
+ ", $o);
+ }
+ ########################################################################
+ function logErrorQuery($statement, $error) {
+ if(!createQueryLogTable()) return;
+
+ $o = [
+ 'silentErrors' => true,
+ 'noSlowQueryLog' => true,
+ 'noErrorQueryLog' => true
+ ];
+ $statement = makeSafe($statement);
+ $error = makeSafe($error);
+ $memberID = makeSafe(getLoggedMemberID());
+ $uri = makeSafe($_SERVER['REQUEST_URI']);
+
+ sql("INSERT INTO `appgini_query_log` SET
+ `statement`='$statement',
+ `error`='$error',
+ `memberID`='$memberID',
+ `uri`='$uri'
+ ", $o);
+ }
+
+ ########################################################################
+ function createQueryLogTable() {
+ static $created = false;
+ if($created) return true;
+
+ $o = [
+ 'silentErrors' => true,
+ 'noSlowQueryLog' => true,
+ 'noErrorQueryLog' => true
+ ];
+
+ sql("CREATE TABLE IF NOT EXISTS `appgini_query_log` (
+ `datetime` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ `statement` LONGTEXT,
+ `duration` DECIMAL(10,2) UNSIGNED DEFAULT 0.0,
+ `error` TEXT,
+ `memberID` VARCHAR(200),
+ `uri` VARCHAR(200)
+ ) CHARSET " . mysql_charset, $o);
+
+ // check if table created
+ //$o2 = $o;
+ //$o2['error'] = '';
+ //sql("SELECT COUNT(1) FROM 'appgini_query_log'", $o2);
+
+ //$created = empty($o2['error']);
+
+ $created = true;
+ return $created;
}
########################################################################
- function sqlValue($statment, &$error = NULL) {
- // executes a statment that retreives a single data value and returns the value retrieved
+ function sqlValue($statement, &$error = NULL) {
+ // executes a statement that retreives a single data value and returns the value retrieved
$eo = ['silentErrors' => true];
- if(!$res = sql($statment, $eo)) { $error = $eo['error']; return false; }
+ if(!$res = sql($statement, $eo)) { $error = $eo['error']; return false; }
if(!$row = db_fetch_row($res)) return false;
return $row[0];
}
diff --git a/app/admin/incHeader.php b/app/admin/incHeader.php
index 3f65631..1c3d45a 100644
--- a/app/admin/incHeader.php
+++ b/app/admin/incHeader.php
@@ -200,13 +200,15 @@ function hideDialogs() {
diff --git a/app/admin/pageBackupRestore.php b/app/admin/pageBackupRestore.php
index afc5955..900b727 100644
--- a/app/admin/pageBackupRestore.php
+++ b/app/admin/pageBackupRestore.php
@@ -129,7 +129,7 @@ protected function utf8ize($mixed) {
*/
protected function get_specified_backup_file() {
$md5_hash = $this->request['md5_hash'];
- if(!preg_match('/^[a-f0-9]{32}$/i', $md5_hash)) return false;
+ if(!preg_match('/^[a-f0-9]{17,32}$/i', $md5_hash)) return false;
$bfile = "{$this->curr_dir}/backups/{$md5_hash}.sql";
if(!is_file($bfile)) return false;
@@ -361,10 +361,10 @@ public function get_backup_files() {
$list = [];
while(false !== ($entry = $d->read())) {
- if(!preg_match('/^[a-f0-9]{32}\.sql$/i', $entry)) continue;
+ if(!preg_match('/^[a-f0-9]{17,32}\.sql$/i', $entry)) continue;
$fts = @filemtime("{$bdir}/{$entry}");
$list[$fts] = array(
- 'md5_hash' => substr($entry, 0, 32),
+ 'md5_hash' => substr($entry, 0, -4),
'datetime' => date($dtf, $fts),
'size' => number_format(@filesize("{$bdir}/{$entry}") / 1024)
);
@@ -388,7 +388,7 @@ public function create_backup() {
$config = ['dbServer' => '', 'dbUsername' => '', 'dbPassword' => '', 'dbDatabase' => ''];
foreach($config as $k => $v) $config[$k] = escapeshellarg(config($k));
- $dump_file = escapeshellarg(normalize_path($this->curr_dir)) . '/backups/' . md5(microtime()) . '.sql';
+ $dump_file = escapeshellarg(normalize_path($this->curr_dir)) . '/backups/' . substr(md5(microtime() . rand(0, 100000)), -17) . '.sql';
$pass_param = ($config['dbPassword'] ? "-p{$config['dbPassword']}" : '');
$this->cmd = "(mysqldump --no-tablespaces -u{$config['dbUsername']} {$pass_param} -h{$config['dbServer']} {$config['dbDatabase']} -r {$dump_file}) 2>&1";
diff --git a/app/admin/pageEditMember.php b/app/admin/pageEditMember.php
index 943d676..c29d45f 100644
--- a/app/admin/pageEditMember.php
+++ b/app/admin/pageEditMember.php
@@ -383,7 +383,7 @@
.parents('.form-group').removeClass('has-error has-success');
},
success: function(resp) {
- if(resp.match(/\
+
', '' . html_attr($_REQUEST['memberID']) . '', $Translation['username invalid']); ?>
-
+
diff --git a/app/cli-update-calculated-fields.php b/app/cli-update-calculated-fields.php
index 85ad3da..9c287f5 100644
--- a/app/cli-update-calculated-fields.php
+++ b/app/cli-update-calculated-fields.php
@@ -43,7 +43,7 @@
$args = [];
for($i = 0; $i < count($argv); $i += 2) {
if(!in_array($argv[$i], $allowed_args)) continue;
- $args[$argv[$i]] = array_map(trim, explode(',', $argv[$i + 1]));
+ $args[$argv[$i]] = array_map('trim', explode(',', $argv[$i + 1]));
}
$calc = calculated_fields();
diff --git a/app/clients_autofill.php b/app/clients_autofill.php
index 825b2af..33b1c52 100644
--- a/app/clients_autofill.php
+++ b/app/clients_autofill.php
@@ -1,5 +1,5 @@
' + (jQuery('#name').val() || '') + '');\n";
$jsReadOnly .= "\tjQuery('#contact').replaceWith('' + (jQuery('#contact').val() || '') + '
');\n";
$jsReadOnly .= "\tjQuery('#title').replaceWith('' + (jQuery('#title').val() || '') + '
');\n";
@@ -351,8 +352,8 @@ function clients_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1, $Al
$noUploads = true;
} elseif(($AllowInsert && !$selected_id) || ($AllowUpdate && $selected_id)) {
- $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', true);"; // temporarily disable form change handler
- $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', false);"; // re-enable form change handler
+ $jsEditable = "\tjQuery('form').eq(0).data('already_changed', true);"; // temporarily disable form change handler
+ $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', false);"; // re-enable form change handler
}
// process combos
diff --git a/app/clients_view.php b/app/clients_view.php
index 1a98baa..76f8d88 100644
--- a/app/clients_view.php
+++ b/app/clients_view.php
@@ -1,5 +1,5 @@
setTimeout(function() { window.location = "index.php?signOut=1"; }, 2000);';
+ echo error_message($Translation['tableAccessDenied']);
exit;
}
@@ -157,23 +156,6 @@
$x->AllowConsoleLog = false;
$x->AllowDVNavigation = true;
- // mm: build the query based on current member's permissions
- $DisplayRecords = $_REQUEST['DisplayRecords'];
- if(!in_array($DisplayRecords, ['user', 'group'])) { $DisplayRecords = 'all'; }
- if($perm['view'] == 1 || ($perm['view'] > 1 && $DisplayRecords == 'user' && !$_REQUEST['NoFilter_x'])) { // view owner only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `clients`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='clients' AND LCASE(`membership_userrecords`.`memberID`)='" . getLoggedMemberID() . "'";
- } elseif($perm['view'] == 2 || ($perm['view'] > 2 && $DisplayRecords == 'group' && !$_REQUEST['NoFilter_x'])) { // view group only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `clients`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='clients' AND `membership_userrecords`.`groupID`='" . getLoggedGroupID() . "'";
- } elseif($perm['view'] == 3) { // view all
- // no further action
- } elseif($perm['view'] == 0) { // view none
- $x->QueryFields = ['Not enough permissions' => 'NEP'];
- $x->QueryFrom = '`clients`';
- $x->QueryWhere = '';
- $x->DefaultSortField = '';
- }
// hook: clients_init
$render = true;
if(function_exists('clients_init')) {
diff --git a/app/common.js b/app/common.js
index 1b7826e..22a3879 100644
--- a/app/common.js
+++ b/app/common.js
@@ -194,15 +194,13 @@ jQuery(function() {
// in table view, hide unnecessary page elements if no records are displayed
if($j('.table_view').length) {
+ var tvHasWarning = $j('.table_view tfoot .alert-warning').length > 0;
+
setInterval(function() {
- if($j('tfoot .alert-warning').length) {
- $j('#Print, #CSV, #tv-tools, thead, tr.success').addClass('hidden');
- $j('.tv-toggle').parent().addClass('hidden');
- return;
- }
+ $j('#Print, #CSV, #tv-tools, .table_view thead, .table_view tr.success')
+ .toggleClass('hidden', tvHasWarning);
- $j('#Print, #CSV, #tv-tools, thead, tr.success').removeClass('hidden');
- $j('.tv-toggle').parent().removeClass('hidden');
+ $j('.tv-toggle').parent().toggleClass('hidden', tvHasWarning);
}, 100);
}
diff --git a/app/datalist.php b/app/datalist.php
index 5170d9e..318b26e 100644
--- a/app/datalist.php
+++ b/app/datalist.php
@@ -157,6 +157,8 @@ function Render() {
if(isset($_REQUEST['record_selector']) && is_array($_REQUEST['record_selector']))
$record_selector = $_REQUEST['record_selector'];
+ $this->applyPermissionsToQuery($DisplayRecords);
+
if($SelectedID && !$Embedded && $this->AllowDVNavigation) {
$setSelectedIDPreviousPage = !empty($_REQUEST['setSelectedIDPreviousPage']);
$setSelectedIDNextPage = !empty($_REQUEST['setSelectedIDNextPage']) && !$setSelectedIDPreviousPage;
@@ -461,7 +463,6 @@ function Render() {
// TV code, only if user has view permission
if($this->Permissions['view']) {
- $QueryHasCustomWhere = (strlen($this->QueryWhere) > 0);
// apply lookup filterers to the query
foreach($this->filterers as $filterer => $caption) {
@@ -477,26 +478,27 @@ function Render() {
// apply quick search to the query
if($SearchString != '') {
- if($Search_x != '') { $FirstRecord = 1; }
-
- if($this->QueryWhere == '')
- $this->QueryWhere = "where ";
- else
- $this->QueryWhere .= " and ";
+ if($Search_x != '') $FirstRecord = 1;
foreach($this->QueryFieldsQS as $fName => $fCaption)
- if(strpos($fName, 'QuerySearchableFields[$fName] = $fCaption;
- $this->QueryWhere .= '(' . implode(" LIKE '%" . makeSafe($SearchString) . "%' or ", array_keys($this->QuerySearchableFields)) . " LIKE '%" . makeSafe($SearchString) . "%')";
+ $sss = makeSafe($SearchString); // safe search string
+
+ if(count($this->QuerySearchableFields))
+ $this->QueryWhere .= ' AND (' .
+ implode(
+ " LIKE '%{$sss}%' OR ",
+ array_keys($this->QuerySearchableFields)
+ ) . " LIKE '%{$sss}%'" .
+ ')';
}
// set query filters
- // $this->QueryWhere might be empty or might contain a clause (starting with WHERE) to retrieve only user/group data
- $QueryHasWhere = preg_match('/^WHERE\s+/i', $this->QueryWhere);
- $WhereNeedsClosing = 0;
+ $filterGroups = [];
for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i += $FiltersPerGroup) { // Number of filters allowed
// test current filter group
$GroupHasFilters = 0;
@@ -515,63 +517,78 @@ function Render() {
}
}
- if($GroupHasFilters) {
- if(stripos($this->QueryWhere, 'where ') === false)
- $this->QueryWhere = 'WHERE (';
- elseif($QueryHasWhere) {
- $this->QueryWhere .= ' and (';
- $QueryHasWhere = 0;
- }
+ if(!$GroupHasFilters) continue;
- $this->QueryWhere .= " " . $FilterAnd[$i] . " (";
+ $filterGroups[] = [
+ 'join' => '',
+ 'filters' => [ /* one or more strings, each describing a filter */ ]
+ ];
+ $currentGroup =& $filterGroups[count($filterGroups) - 1];
- for($j = 0; $j < $FiltersPerGroup; $j++) {
- $ij = $i + $j;
- if($FilterField[$ij] != '' && $this->QueryFieldsIndexed[($FilterField[$ij])] != '' && $FilterOperator[$ij] != '' && ($FilterValue[$ij] != '' || strpos($FilterOperator[$ij], 'empty'))) {
- if($FilterAnd[$ij] == '') {
- $FilterAnd[$ij] = 'and';
- }
- // test for date/time fields
- $tries = 0; $isDateTime = $isDate = false;
- $fieldName = str_replace('`', '', $this->QueryFieldsIndexed[($FilterField[$ij])]);
- list($tn, $fn) = explode('.', $fieldName);
- while(!($res = sql("SHOW COLUMNS FROM `{$tn}` LIKE '{$fn}'", $eo)) && $tries < 2) {
- $tn = substr($tn, 0, -1);
- $tries++;
- }
- if($res !== false && $row = @db_fetch_array($res)) {
- $isDateTime = in_array($row['Type'], array('date', 'time', 'datetime'));
- $isDate = in_array($row['Type'], ['date', 'datetime']);
- }
- // end of test
- if($FilterOperator[$ij] == 'is-empty' && !$isDateTime) {
- $this->QueryWhere .= ' ' . $FilterAnd[$ij] . ' (' . $this->QueryFieldsIndexed[($FilterField[$ij])] . "='' or " . $this->QueryFieldsIndexed[($FilterField[$ij])] . ' is NULL) ';
- } elseif($FilterOperator[$ij] == 'is-not-empty' && !$isDateTime) {
- $this->QueryWhere .= ' ' . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . "!='' ";
- } elseif($FilterOperator[$ij] == 'is-empty' && $isDateTime) {
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " (" . $this->QueryFieldsIndexed[($FilterField[$ij])] . "=0 or " . $this->QueryFieldsIndexed[($FilterField[$ij])] . " is NULL) ";
- } elseif($FilterOperator[$ij] == 'is-not-empty' && $isDateTime) {
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . "!=0 ";
- } elseif($FilterOperator[$ij] == 'like' && !strstr($FilterValue[$ij], "%") && !strstr($FilterValue[$ij], "_")) {
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . " like '%" . makeSafe($FilterValue[$ij]) . "%' ";
- } elseif($FilterOperator[$ij] == 'not-like' && !strstr($FilterValue[$ij], "%") && !strstr($FilterValue[$ij], "_")) {
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . " not like '%" . makeSafe($FilterValue[$ij]) . "%' ";
- } elseif($isDate) {
- $dateValue = mysql_datetime($FilterValue[$ij]);
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$ij]] . " '$dateValue' ";
- } else {
- $this->QueryWhere .= " " . $FilterAnd[$ij] . " " . $this->QueryFieldsIndexed[($FilterField[$ij])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$ij]] . " '" . makeSafe($FilterValue[$ij]) . "' ";
- }
- }
- }
+ for($j = 0; $j < $FiltersPerGroup; $j++) {
+ $ij = $i + $j;
+
+ // not a valid filter?
+ if(
+ !$FilterField[$ij] ||
+ !$this->QueryFieldsIndexed[($FilterField[$ij])] ||
+ !$FilterOperator[$ij] ||
+ (!$FilterValue[$ij] && strpos($FilterOperator[$ij], 'empty') === false)
+ ) continue;
+
+ if($FilterAnd[$ij] == '') $FilterAnd[$ij] = 'and';
+ $currentGroup['filters'][] = '';
+ $currentFilter =& $currentGroup['filters'][count($currentGroup['filters']) - 1];
+
+ // always use the 1st FilterAnd of the group as the group's join
+ if(empty($currentGroup['join'])) $currentGroup['join'] = thisOr($FilterAnd[$i], 'and');
+
+ // if this is NOT the first filter in the group, add its FilterAnd, else ignore
+ if(count($currentGroup['filters']) > 1)
+ $currentFilter = $FilterAnd[$ij] . ' ';
+
+ list($isDate, $isDateTime) = $this->fieldIsDateTime($FilterField[$ij]);
+
+ if($FilterOperator[$ij] == 'is-empty' && !$isDateTime)
+ $currentFilter .= '(' . $this->QueryFieldsIndexed[($FilterField[$ij])] . "='' OR " . $this->QueryFieldsIndexed[($FilterField[$ij])] . ' IS NULL)';
+
+ elseif($FilterOperator[$ij] == 'is-not-empty' && !$isDateTime)
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . "!=''";
+
+ elseif($FilterOperator[$ij] == 'is-empty' && $isDateTime)
+ $currentFilter .= '(' . $this->QueryFieldsIndexed[($FilterField[$ij])] . "=0 OR " . $this->QueryFieldsIndexed[($FilterField[$ij])] . ' IS NULL)';
+
+ elseif($FilterOperator[$ij] == 'is-not-empty' && $isDateTime)
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . "!=0";
+
+ elseif($FilterOperator[$ij] == 'like' && !strstr($FilterValue[$ij], "%") && !strstr($FilterValue[$ij], "_"))
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . " LIKE '%" . makeSafe($FilterValue[$ij]) . "%'";
+
+ elseif($FilterOperator[$ij] == 'not-like' && !strstr($FilterValue[$ij], "%") && !strstr($FilterValue[$ij], "_"))
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . " NOT LIKE '%" . makeSafe($FilterValue[$ij]) . "%'";
+
+ elseif($isDate) {
+ $dateValue = mysql_datetime($FilterValue[$ij]);
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . ' ' . $GLOBALS['filter_operators'][$FilterOperator[$ij]] . " '$dateValue'";
+
+ } else
+ $currentFilter .= $this->QueryFieldsIndexed[($FilterField[$ij])] . ' ' . $GLOBALS['filter_operators'][$FilterOperator[$ij]] . " '" . makeSafe($FilterValue[$ij]) . "'";
- $this->QueryWhere .= ") ";
- $WhereNeedsClosing = 1;
}
}
- if($WhereNeedsClosing && !$QueryHasCustomWhere)
- $this->QueryWhere .= ")";
+ // construct filters from $filterGroups
+ $filtersWhere = '';
+ foreach($filterGroups as $fg) {
+ if(empty($fg['filters'])) continue;
+
+ // ignore 1st join (i.e. use it only if filtersWhere already populated)
+ if($filtersWhere) $filtersWhere .= " {$fg['join']} ";
+
+ $filtersWhere .= '(' . implode(' ', $fg['filters']) . ')';
+ }
+
+ if($filtersWhere) $this->QueryWhere .= " AND ($filtersWhere)";
// set query sort
if(!stristr($this->QueryOrder, "order by ") && $SortField != '' && $this->AllowSorting) {
@@ -580,22 +597,12 @@ function Render() {
$actualSortField = str_replace(" $fieldNum ", " $fieldSort ", " $actualSortField ");
$actualSortField = str_replace(",$fieldNum ", ",$fieldSort ", " $actualSortField ");
}
- $this->QueryOrder = "order by $actualSortField $SortDirection";
+ $this->QueryOrder = "ORDER BY $actualSortField $SortDirection";
}
- // clean up query
- $this->QueryWhere = str_replace('( and ', '( ', $this->QueryWhere);
- $this->QueryWhere = str_replace('( or ', '( ', $this->QueryWhere);
- $this->QueryWhere = str_replace('( and ', '( ', $this->QueryWhere);
- $this->QueryWhere = str_replace('( or ', '( ', $this->QueryWhere);
- $this->QueryWhere = str_replace('', '', $this->QueryWhere);
- $this->QueryWhere = str_replace('', '', $this->QueryWhere);
- $this->QueryWhere = str_replace('', '', $this->QueryWhere);
- $this->QueryWhere = str_replace('', '', $this->QueryWhere);
-
// if no 'order by' clause found, apply default sorting if specified
if($this->DefaultSortField != '' && $this->QueryOrder == '') {
- $this->QueryOrder = "order by {$this->DefaultSortField} {$this->DefaultSortDirection}";
+ $this->QueryOrder = "ORDER BY {$this->DefaultSortField} {$this->DefaultSortDirection}";
}
// Output CSV on request
@@ -1187,7 +1194,12 @@ function Render() {
);
if($dvCode) {
- $this->HTML .= "\n\tTableName}\" class=\"col-xs-12 table-{$this->TableName} detail_view {$this->DVClasses}\">{$tv_dv_separator}
{$dvCode}
";
+ $this->HTML .= sprintf(
+ '',
+ $this->TableName, $this->TableName, $this->DVClasses, $tv_dv_separator,
+ $dvCode == $this->translation['tableAccessDenied'] ? 'alert alert-danger' : 'panel panel-default',
+ $dvCode
+ );
$this->ContentType = 'detailview';
$dvShown = true;
}
@@ -1210,7 +1222,7 @@ function Render() {
// handle the case were user has no view access and has just inserted a record
// by redirecting to tablename_view.php (which should redirect them to insert form)
- if(!$this->Permissions['view'] && !$dvCode && $SelectedID && isset($_REQUEST['record-added-ok'])) {
+ if(!$this->Permissions['view'] && (!$dvCode || $dvCode == $this->translation['tableAccessDenied']) && $SelectedID && isset($_REQUEST['record-added-ok'])) {
ob_start();
?>';
+ echo error_message($Translation['tableAccessDenied']);
exit;
}
@@ -127,23 +126,6 @@
$x->AllowConsoleLog = false;
$x->AllowDVNavigation = true;
- // mm: build the query based on current member's permissions
- $DisplayRecords = $_REQUEST['DisplayRecords'];
- if(!in_array($DisplayRecords, ['user', 'group'])) { $DisplayRecords = 'all'; }
- if($perm['view'] == 1 || ($perm['view'] > 1 && $DisplayRecords == 'user' && !$_REQUEST['NoFilter_x'])) { // view owner only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `invoice_items`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='invoice_items' AND LCASE(`membership_userrecords`.`memberID`)='" . getLoggedMemberID() . "'";
- } elseif($perm['view'] == 2 || ($perm['view'] > 2 && $DisplayRecords == 'group' && !$_REQUEST['NoFilter_x'])) { // view group only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `invoice_items`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='invoice_items' AND `membership_userrecords`.`groupID`='" . getLoggedGroupID() . "'";
- } elseif($perm['view'] == 3) { // view all
- // no further action
- } elseif($perm['view'] == 0) { // view none
- $x->QueryFields = ['Not enough permissions' => 'NEP'];
- $x->QueryFrom = '`invoice_items`';
- $x->QueryWhere = '';
- $x->DefaultSortField = '';
- }
// hook: invoice_items_init
$render = true;
if(function_exists('invoice_items_init')) {
diff --git a/app/invoices_autofill.php b/app/invoices_autofill.php
index c9ef280..49f7800 100644
--- a/app/invoices_autofill.php
+++ b/app/invoices_autofill.php
@@ -1,5 +1,5 @@
Request::val('code', ''),
'status' => Request::val('status', 'Unpaid'),
'date_due' => Request::dateComponents('date_due', '1'),
- 'client' => Request::val('client', ''),
+ 'client' => Request::lookup('client', ''),
'client_contact' => Request::lookup('client'),
'client_address' => Request::lookup('client'),
'client_phone' => Request::lookup('client'),
@@ -164,7 +164,7 @@ function invoices_update(&$selected_id, &$error_message = '') {
'code' => Request::val('code', ''),
'status' => Request::val('status', ''),
'date_due' => Request::dateComponents('date_due', ''),
- 'client' => Request::val('client', ''),
+ 'client' => Request::lookup('client', ''),
'client_contact' => Request::lookup('client'),
'client_address' => Request::lookup('client'),
'client_phone' => Request::lookup('client'),
@@ -246,7 +246,7 @@ function invoices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1, $A
// mm: get table permissions
$arrPerm = getTablePermissions('invoices');
- if(!$arrPerm['insert'] && $selected_id=='') { return ''; }
+ if(!$arrPerm['insert'] && $selected_id=='') return $Translation['tableAccessDenied'];
$AllowInsert = ($arrPerm['insert'] ? true : false);
// print preview?
$dvprint = false;
@@ -304,14 +304,14 @@ function invoices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1, $A
if($selected_id) {
// mm: check member permissions
- if(!$arrPerm['view']) return '';
+ if(!$arrPerm['view']) return $Translation['tableAccessDenied'];
// mm: who is the owner?
$ownerGroupID = sqlValue("SELECT `groupID` FROM `membership_userrecords` WHERE `tableName`='invoices' AND `pkValue`='" . makeSafe($selected_id) . "'");
$ownerMemberID = sqlValue("SELECT LCASE(`memberID`) FROM `membership_userrecords` WHERE `tableName`='invoices' AND `pkValue`='" . makeSafe($selected_id) . "'");
- if($arrPerm['view'] == 1 && getLoggedMemberID() != $ownerMemberID) return '';
- if($arrPerm['view'] == 2 && getLoggedGroupID() != $ownerGroupID) return '';
+ if($arrPerm['view'] == 1 && getLoggedMemberID() != $ownerMemberID) return $Translation['tableAccessDenied'];
+ if($arrPerm['view'] == 2 && getLoggedGroupID() != $ownerGroupID) return $Translation['tableAccessDenied'];
// can edit?
$AllowUpdate = 0;
@@ -344,7 +344,7 @@ function invoices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1, $A
';
+ echo error_message($Translation['tableAccessDenied']);
exit;
}
@@ -182,23 +181,6 @@
$x->AllowConsoleLog = false;
$x->AllowDVNavigation = true;
- // mm: build the query based on current member's permissions
- $DisplayRecords = $_REQUEST['DisplayRecords'];
- if(!in_array($DisplayRecords, ['user', 'group'])) { $DisplayRecords = 'all'; }
- if($perm['view'] == 1 || ($perm['view'] > 1 && $DisplayRecords == 'user' && !$_REQUEST['NoFilter_x'])) { // view owner only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `invoices`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='invoices' AND LCASE(`membership_userrecords`.`memberID`)='" . getLoggedMemberID() . "'";
- } elseif($perm['view'] == 2 || ($perm['view'] > 2 && $DisplayRecords == 'group' && !$_REQUEST['NoFilter_x'])) { // view group only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `invoices`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='invoices' AND `membership_userrecords`.`groupID`='" . getLoggedGroupID() . "'";
- } elseif($perm['view'] == 3) { // view all
- // no further action
- } elseif($perm['view'] == 0) { // view none
- $x->QueryFields = ['Not enough permissions' => 'NEP'];
- $x->QueryFrom = '`invoices`';
- $x->QueryWhere = '';
- $x->DefaultSortField = '';
- }
// hook: invoices_init
$render = true;
if(function_exists('invoices_init')) {
diff --git a/app/item_prices_autofill.php b/app/item_prices_autofill.php
index 02ad2bf..43b8fe0 100644
--- a/app/item_prices_autofill.php
+++ b/app/item_prices_autofill.php
@@ -1,5 +1,5 @@
Request::val('item', ''),
+ 'item' => Request::lookup('item', ''),
'price' => Request::val('price', '0.00'),
'date' => Request::dateComponents('date', '1'),
];
@@ -109,7 +109,7 @@ function item_prices_update(&$selected_id, &$error_message = '') {
if(!check_record_permission('item_prices', $selected_id, 'edit')) return false;
$data = [
- 'item' => Request::val('item', ''),
+ 'item' => Request::lookup('item', ''),
'price' => Request::val('price', ''),
'date' => Request::dateComponents('date', ''),
];
@@ -177,7 +177,7 @@ function item_prices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1,
// mm: get table permissions
$arrPerm = getTablePermissions('item_prices');
- if(!$arrPerm['insert'] && $selected_id=='') { return ''; }
+ if(!$arrPerm['insert'] && $selected_id=='') return $Translation['tableAccessDenied'];
$AllowInsert = ($arrPerm['insert'] ? true : false);
// print preview?
$dvprint = false;
@@ -204,14 +204,14 @@ function item_prices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1,
if($selected_id) {
// mm: check member permissions
- if(!$arrPerm['view']) return '';
+ if(!$arrPerm['view']) return $Translation['tableAccessDenied'];
// mm: who is the owner?
$ownerGroupID = sqlValue("SELECT `groupID` FROM `membership_userrecords` WHERE `tableName`='item_prices' AND `pkValue`='" . makeSafe($selected_id) . "'");
$ownerMemberID = sqlValue("SELECT LCASE(`memberID`) FROM `membership_userrecords` WHERE `tableName`='item_prices' AND `pkValue`='" . makeSafe($selected_id) . "'");
- if($arrPerm['view'] == 1 && getLoggedMemberID() != $ownerMemberID) return '';
- if($arrPerm['view'] == 2 && getLoggedGroupID() != $ownerGroupID) return '';
+ if($arrPerm['view'] == 1 && getLoggedMemberID() != $ownerMemberID) return $Translation['tableAccessDenied'];
+ if($arrPerm['view'] == 2 && getLoggedGroupID() != $ownerGroupID) return $Translation['tableAccessDenied'];
// can edit?
$AllowUpdate = 0;
@@ -238,7 +238,7 @@ function item_prices_form($selected_id = '', $AllowUpdate = 1, $AllowInsert = 1,
';
+ echo error_message($Translation['tableAccessDenied']);
exit;
}
@@ -107,23 +106,6 @@
$x->AllowConsoleLog = false;
$x->AllowDVNavigation = true;
- // mm: build the query based on current member's permissions
- $DisplayRecords = $_REQUEST['DisplayRecords'];
- if(!in_array($DisplayRecords, ['user', 'group'])) { $DisplayRecords = 'all'; }
- if($perm['view'] == 1 || ($perm['view'] > 1 && $DisplayRecords == 'user' && !$_REQUEST['NoFilter_x'])) { // view owner only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `item_prices`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='item_prices' AND LCASE(`membership_userrecords`.`memberID`)='" . getLoggedMemberID() . "'";
- } elseif($perm['view'] == 2 || ($perm['view'] > 2 && $DisplayRecords == 'group' && !$_REQUEST['NoFilter_x'])) { // view group only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `item_prices`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='item_prices' AND `membership_userrecords`.`groupID`='" . getLoggedGroupID() . "'";
- } elseif($perm['view'] == 3) { // view all
- // no further action
- } elseif($perm['view'] == 0) { // view none
- $x->QueryFields = ['Not enough permissions' => 'NEP'];
- $x->QueryFrom = '`item_prices`';
- $x->QueryWhere = '';
- $x->DefaultSortField = '';
- }
// hook: item_prices_init
$render = true;
if(function_exists('item_prices_init')) {
diff --git a/app/items_autofill.php b/app/items_autofill.php
index ddd7b47..07fccf1 100644
--- a/app/items_autofill.php
+++ b/app/items_autofill.php
@@ -1,5 +1,5 @@
' + (jQuery('#item_description').val() || '') + '');\n";
$jsReadOnly .= "\tjQuery('.select2-container').hide();\n";
$noUploads = true;
} elseif($AllowInsert) {
- $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', true);"; // temporarily disable form change handler
- $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', false);"; // re-enable form change handler
+ $jsEditable = "\tjQuery('form').eq(0).data('already_changed', true);"; // temporarily disable form change handler
+ $jsEditable .= "\tjQuery('form').eq(0).data('already_changed', false);"; // re-enable form change handler
}
// process combos
diff --git a/app/items_view.php b/app/items_view.php
index 4c89b7c..aa46c4c 100644
--- a/app/items_view.php
+++ b/app/items_view.php
@@ -1,5 +1,5 @@
setTimeout(function() { window.location = "index.php?signOut=1"; }, 2000);';
+ echo error_message($Translation['tableAccessDenied']);
exit;
}
@@ -102,23 +101,6 @@
$x->AllowConsoleLog = false;
$x->AllowDVNavigation = true;
- // mm: build the query based on current member's permissions
- $DisplayRecords = $_REQUEST['DisplayRecords'];
- if(!in_array($DisplayRecords, ['user', 'group'])) { $DisplayRecords = 'all'; }
- if($perm['view'] == 1 || ($perm['view'] > 1 && $DisplayRecords == 'user' && !$_REQUEST['NoFilter_x'])) { // view owner only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `items`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='items' AND LCASE(`membership_userrecords`.`memberID`)='" . getLoggedMemberID() . "'";
- } elseif($perm['view'] == 2 || ($perm['view'] > 2 && $DisplayRecords == 'group' && !$_REQUEST['NoFilter_x'])) { // view group only
- $x->QueryFrom .= ', `membership_userrecords`';
- $x->QueryWhere = "WHERE `items`.`id`=`membership_userrecords`.`pkValue` AND `membership_userrecords`.`tableName`='items' AND `membership_userrecords`.`groupID`='" . getLoggedGroupID() . "'";
- } elseif($perm['view'] == 3) { // view all
- // no further action
- } elseif($perm['view'] == 0) { // view none
- $x->QueryFields = ['Not enough permissions' => 'NEP'];
- $x->QueryFrom = '`items`';
- $x->QueryWhere = '';
- $x->DefaultSortField = '';
- }
// hook: items_init
$render = true;
if(function_exists('items_init')) {
diff --git a/app/language.php b/app/language.php
index e974782..0e781a8 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',
@@ -797,4 +797,16 @@
'Mail' => 'Mail',
'Preconfigured users and groups' => 'Preconfigured users and groups',
'Application' => 'Application',
+
+ // Added in 5.97
+ 'Query logs' => 'Query logs',
+ 'Query log table does not exist' => 'There was an error while attempting to create appgini_query_log table. Please make sure the database user has CREATE TABLE privilege.',
+ 'slow queries' => 'Slow queries',
+ 'error queries' => 'Error queries',
+ 'date/time' => 'Date/time',
+ 'duration (sec)' => 'Duration (sec)',
+ 'page address' => 'Page address',
+ 'query' => 'Query',
+ 'page x of y' => 'Page of ',
+ 'total # queries' => 'Total # queries.',
];
\ No newline at end of file
diff --git a/app/lib.php b/app/lib.php
index 221a51a..4a01dd8 100644
--- a/app/lib.php
+++ b/app/lib.php
@@ -1,5 +1,5 @@
/)) {
+ if(resp.indexOf('username-available') > -1) {
reset_username_status('success');
} else {
reset_username_status('error');
diff --git a/app/parent-children.php b/app/parent-children.php
index 7f95743..06787b8 100644
--- a/app/parent-children.php
+++ b/app/parent-children.php
@@ -1,5 +1,5 @@
+
diff --git a/app/templates/invoice_items_templateDVP.html b/app/templates/invoice_items_templateDVP.html
index 70deab4..0d1996c 100644
--- a/app/templates/invoice_items_templateDVP.html
+++ b/app/templates/invoice_items_templateDVP.html
@@ -72,3 +72,14 @@
+
diff --git a/app/templates/invoices_templateDVP.html b/app/templates/invoices_templateDVP.html
index 82dc409..7f537b1 100644
--- a/app/templates/invoices_templateDVP.html
+++ b/app/templates/invoices_templateDVP.html
@@ -149,6 +149,17 @@
+
diff --git a/app/templates/item_prices_templateDVP.html b/app/templates/item_prices_templateDVP.html
index f5bd35f..78be282 100644
--- a/app/templates/item_prices_templateDVP.html
+++ b/app/templates/item_prices_templateDVP.html
@@ -44,3 +44,14 @@
+
diff --git a/app/templates/items_templateDVP.html b/app/templates/items_templateDVP.html
index 90a2892..4cf4f31 100644
--- a/app/templates/items_templateDVP.html
+++ b/app/templates/items_templateDVP.html
@@ -37,6 +37,17 @@
+
diff --git a/online-invoicing-system.axp b/online-invoicing-system.axp
index 548341d..ac88339 100644
--- a/online-invoicing-system.axp
+++ b/online-invoicing-system.axp
@@ -1,4 +1,4 @@
-
online_inovicing_systemlocalhostUTF-8False12FalseEurope/LondonFalseFalseTrueFalsebootstrap.cssFalseTrue0245.952021-03-28 18:44:41C:\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.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_comments400FalseFalseFalseFalseFalseTrueFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalsecommentsidclientsTrueFalseTrue01TrueFalseFalseFalse7False00FalseFalseleft0000Falsesubtotal892FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseTrue14False00FalseFalseright0000Truediscount842FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse13False00FalseFalseright0000Falsetax892FalseFalseFalseFalseFalseFalseFalseFalse150False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01TrueFalseFalseFalse15False00FalseFalseright0000Falsetotal892FalseFalseFalseFalseFalseFalseTrueFalse70False0FalseFalseFalseFalse00FalseFalseFalse00FalseFalse0FalseFalseFalseFalseFalse01FalseFalseFalseTrue9False00FalseFalseright0000True