Jelajahi Sumber

Merge with branch prod (Milestone 1)

Piotr Labudda 11 tahun lalu
induk
melakukan
cae2a5c2bd
4 mengubah file dengan 447 tambahan dan 255 penghapusan
  1. 40 0
      SE/se-lib/DataSourceFactory.php
  2. 80 7
      SE/se-lib/Data_Source.php
  3. 134 52
      SE/se-lib/TableAcl.php
  4. 193 196
      SE/se-lib/TableAjax.php

+ 40 - 0
SE/se-lib/DataSourceFactory.php

@@ -0,0 +1,40 @@
+<?php
+
+Lib::loadClass('Data_Source');
+
+class DataSourceFactory {
+
+	public static function buildFromZasobInfo($dsConfig) {
+		if (empty($dsConfig['source_id'])) {
+			throw new Exception("Source id not defined - can't create Data Source!");
+		}
+		if (empty($dsConfig['object_name'])) {
+			throw new Exception("object name not defined - can't create Data Source!");
+		}
+
+		$dataSource = new Data_Source($dsConfig['source_id']);
+		$dataSource->setTable($dsConfig['object_name']);
+		if (!empty($dsConfig['fields'])) {
+			$dataSource->setCols($dsConfig['fields']);
+		}
+		if (!empty($dsConfig['field_types'])) {
+			$dataSource->setColTypes($dsConfig['field_types']);
+		}
+		if (!empty($dsConfig['fields_virtual'])) {
+			$dataSource->setVirtualCols($dsConfig['fields_virtual']);
+		}
+		if (isset($dsConfig['acl_fltr_allowed'])) {
+			$dataSource->setAccessFltrAllowed($dsConfig['acl_fltr_allowed']);
+		}
+		return $dataSource;
+	}
+
+	public static function buildFromZasobId($config) {
+		throw new Exception("TODO: " . __CLASS__ . "::" . __FUNCTION__ . "() ...");
+	}
+
+	public static function buildFromXsd($config) {
+		throw new Exception("TODO: " . __CLASS__ . "::" . __FUNCTION__ . "() ...");
+	}
+
+}

+ 80 - 7
SE/se-lib/Data_Source.php

@@ -59,6 +59,42 @@ class Data_Source {
 		return $this->_cols;
 	}
 
+	public function getFieldTypes() {
+		$fieldTypes = array();
+		//$dbID = $this->getDB();
+		$db = $this->_db;//(TableAcl) DB::getDB($dbID);
+		$tblName = $this->_tbl;//(TableAcl) $this->getName();
+		if (!$db) {
+			throw new Exception('DataSource is not defined');
+		}
+		$res = $db->query("show fields from `{$tblName}` ");
+		while ($h = $db->fetch_row($res)) {
+			$fieldName = $h[0];
+			$fieldType = $h[1];
+			$fieldTypes[$fieldName] = array('type'=>$h[1], 'null'=>('YES' == $h[2]), 'default'=>$h[4]);
+		}
+		return $fieldTypes;
+	}
+
+	public function getUniqueKeys() {
+		$sqlKeys = array();
+		//$dbID = $this->getDB();
+		$db = $this->_db;//(TableAcl) DB::getDB($dbID);
+		$tblName = $this->_tbl;//(TableAcl) $this->getName();
+		if (!$db) {
+			throw new Exception('DataSource is not defined');
+		}
+		$sql = "SHOW KEYS FROM  `{$tblName}`";
+		$res = $db->query($sql);
+		while ($r = $db->fetch($res)) {
+			if ($r->Non_unique == '0') {
+				$sqlKeys[$r->Column_name] = true;
+			}
+		}
+		$sqlKeys = array_keys($sqlKeys);
+		return $sqlKeys;
+	}
+
 	function set_cols($cols) {// TODO: RMME
 		$this->setCols($cols);
 	}
@@ -627,8 +663,8 @@ if(V::get('DBG_DS', 0, $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 
 	public function add_col($col_name, $label = '', $type = 'string') {
 		if (!$label) $label = $col_name;
-		$this->_cols [$col_name] = $label;
-		$this->_col_types [$col_name] = $type;
+		$this->_cols[$col_name] = $label;
+		$this->_col_types[$col_name] = $type;
 	}
 
 	public function addCol($col_name) {
@@ -729,16 +765,53 @@ if(V::get('DBG_DS', 0, $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 		return ('_CSV_NUM' == substr($fldName, -8));
 	}
 
-	public function updateItem($item) {
-		if (!$item->ID) return false;
+	public function updateItem($itemPatch) {
+		// TODO: $item as array and copy from $db->UPDATE_OBJ using PDO
+		if (is_object($itemPatch)) {
+			$itemPatch = (array)$itemPatch;
+		} else if (!is_array($itemPatch)) {
+			throw new HttpException('Item patch is not array', 400);
+		}
+		if (empty($itemPatch)) {
+			//throw new Exception('Item patch is empty');
+			return 0;// nothing to change
+		}
+
+		$primaryKeyField = $this->getPrimaryKeyField();
+		if (empty($itemPatch[$primaryKeyField])) {
+			throw new HttpException("Item Primary Key not set!", 400);
+		}
 
-		$affected = $this->_db->UPDATE_OBJ($this->_tbl, $item);
+		$itemPatch = (object)$itemPatch;
+		$affected = $this->_db->UPDATE_OBJ($this->_tbl, $itemPatch);
+		if ($affected < 0) {
+			$dsErrors = $ds->getDbErrors();
+			$dsErrors = "Wystąpiły błędy!\n" . implode("\n", $dsErrors);
+			throw new Exception($dsErrors);
+		}
 		return $affected;
 	}
 
 	public function addItem($item) {
-		$id = $this->_db->ADD_NEW_OBJ($this->_tbl, $item);
-		return $id;
+		// TODO: $item as array and copy from $db->ADD_NEW_OBJ using PDO
+		if (is_object($item)) {
+			$item = (array)$item;
+		} else if (!is_array($item)) {
+			throw new HttpException('Item is not array', 400);
+		}
+		if (empty($item)) {
+			//throw new Exception('Item patch is empty');
+			return 0;// nothing to insert
+		}
+
+		$item = (object)$item;
+		$primaryKey = $this->_db->ADD_NEW_OBJ($this->_tbl, $item);
+		if ($primaryKey < 0) {
+			$dsErrors = $this->getDbErrors();
+			$dsErrors = "Wystąpiły błędy!\n" . implode("\n", $dsErrors);
+			throw new Exception($dsErrors);
+		}
+		return $primaryKey;
 	}
 
 	public function getDbErrors() {

+ 134 - 52
SE/se-lib/TableAcl.php

@@ -619,18 +619,9 @@ class TableAcl {
 		if ($this->isInitialized() && $force == false) {
 			return;
 		}
-		$dbID = $this->getDB();
-		$tblName = $this->getName();
-		$db = DB::getDB($dbID);
-		if (!$db) {
-			die('Error - Brak konfiguracji dla bazy danych ID=' . $dbID);
-		}
-		$res = $db->query("show fields from `{$tblName}` ");
-		while ($h = $db->fetch_row($res)) {
-			$fieldName = $h[0];
-			$fieldType = $h[1];
-			$this->_types[$fieldName] = array('type'=>$h[1], 'null'=>('YES' == $h[2]), 'default'=>$h[4]);
-		}
+		$ds = $this->getDataSource();
+		$this->_types = $ds->getFieldTypes();
+
 		uasort($this->_fields, array($this, 'sortFieldsCallback'));
 
 		$this->_fixDateFields();
@@ -714,7 +705,7 @@ class TableAcl {
 		}
 	}
 
-	public function getUniqueKeys() {
+	public function getUniqueKeys() {// TODO: RM NOT USED?
 		$sqlKeys = array();
 		$dbID = $this->getDB();
 		$tblName = $this->getName();
@@ -933,37 +924,20 @@ class TableAcl {
 	}
 
 	public function convertObjectFromUserInput($args, $type = 'array_by_id', $prefix = 'f') {
-		$obj = new stdClass();
+		$item = array();
 		$fields = $this->getFields();
 		foreach ($fields as $kID => $vField) {
-			if (!$this->isAllowed($kID, 'C')) {
-				continue;
-			}
+			$vFieldName = $vField['name'];
 			if (array_key_exists("f{$kID}", $args)) {
-				$obj->{$vField['name']} = $args["f{$kID}"];
+				$value = $args["f{$kID}"];
 
 				if (empty($args["f{$kID}"]) && strlen($args["f{$kID}"]) == 0) {// fix bug in input type date and value="0000-00-00"
-					$obj->{$vField['name']} = $this->fixEmptyValueFromUser($kID);
-				}
-			}
-		}
-
-		{// add DefaultAclGroup if no create perms ('C')
-			$defaultAclGroup = User::getDefaultAclGroup();
-			if ($defaultAclGroup) {
-				foreach ($fields as $kID => $vField) {
-					if (!$this->isAllowed($kID, 'C')) {
-						if ($vField['name'] == 'A_ADM_COMPANY') {
-							$obj->{$vField['name']} = $defaultAclGroup;
-						}
-						else if ($vField['name'] == 'A_CLASSIFIED') {
-							$obj->{$vField['name']} = $defaultAclGroup;
-						}
-					}
+					$value = $this->fixEmptyValueFromUser($kID);
 				}
+				$item[$vFieldName] = $value;
 			}
 		}
-		return $obj;
+		return $item;
 	}
 
 	public function getItem($id) {
@@ -1006,27 +980,129 @@ class TableAcl {
 		return $ds->getHistItems($id);
 	}
 
-	public function addItem($item) {
+	public function addItem($itemTodo) {
+		if (is_object($itemTodo)) {
+			$itemTodo = (array)$itemTodo;
+		} else if (!is_array($itemTodo)) {
+			throw new HttpException('Item is not array', 400);
+		}
+		if (empty($itemTodo)) {
+			//throw new Exception('Item patch is empty');
+			return 0;// nothing to insert
+		}
 		$ds = $this->getDataSource();
+
+		// from convertObjectFromUserInput
+		$item = array();
+		$fields = $this->getFields();
+		foreach ($fields as $kID => $vField) {
+			$vFieldName = $vField['name'];
+			if (!$this->isAllowed($kID, 'C')) {
+				continue;
+			}
+			if (isset($itemTodo[$vFieldName])) {
+				$value = $itemTodo[$vFieldName];
+
+				if (empty($value) && strlen($value) == 0) {// fix bug in input type date and value="0000-00-00"
+					$value = $this->fixEmptyValueFromUser($kID);
+				}
+				$item[$vFieldName] = $value;
+			}
+		}
+		if (empty($item)) {
+			throw new Exception("Nothing to add");
+		}
+
+		{// add DefaultAclGroup if no create perms ('C')
+			$defaultAclGroup = User::getDefaultAclGroup();
+			if ($defaultAclGroup) {
+				foreach ($fields as $kID => $vField) {
+					$vFieldName = $vField['name'];
+					if (!$this->isAllowed($kID, 'C')) {
+						if ($vFieldName == 'A_ADM_COMPANY') {
+							$item[$vFieldName] = $defaultAclGroup;
+						}
+						else if ($vFieldName == 'A_CLASSIFIED') {
+							$item[$vFieldName] = $defaultAclGroup;
+						}
+					}
+				}
+			}
+		}
+
 		return $ds->addItem($item);
 	}
 
+	/**
+	 * @param array $itemPatch
+	 */
 	public function updateItem($itemPatch) {
+		if (is_object($itemPatch)) {
+			$itemPatch = (array)$itemPatch;
+		} else if (!is_array($itemPatch)) {
+			throw new HttpException('Item patch is not array', 400);
+		}
+		if (empty($itemPatch)) {
+			//throw new Exception('Item patch is empty');
+			return 0;// nothing to change
+		}
+
 		$ds = $this->getDataSource();
-		$affected = $ds->updateItem($itemPatch);
-		if ($affected < 0) {
-			$dsErrors = $ds->getDbErrors();
-			$dsErrors = "Wystąpiły błędy!\n" . implode("\n", $dsErrors);
-			throw new Exception($dsErrors);
+		$primaryKeyField = $ds->getPrimaryKeyField();
+		if (empty($itemPatch[$primaryKeyField])) {
+			throw new HttpException("Item Primary Key not set!", 400);
+		}
+
+		$primaryKey = $itemPatch[$primaryKeyField];
+		$itemOld = $this->getItem($primaryKey);
+		if (!$itemOld) {
+			throw new HttpException("Item not exists!", 404);
 		}
+
+		if (!$this->canWriteRecord($itemOld) && !$this->hasPermSuperWrite()) {
+			throw new HttpException("Brak dostępu do rekordu", 403);
+		}
+
+		// $itemPatch from user input to $itemPatchChecked
+		$itemPatchChecked = array();
+		$fields = $this->getFields();
+		foreach ($fields as $kID => $vField) {
+			$vFieldName = $vField['name'];
+			if (!$this->isAllowed($kID, 'W', $itemOld)) {
+				continue;
+			}
+			if (isset($itemPatch[$vFieldName])) {
+				if (!$this->isAllowed($kID, 'R', $itemOld) && '*****' == $itemPatch[$vFieldName]) {
+					// default value for perms 'W' without 'R' is '*****'
+				}
+				else {
+					$value = $itemPatch[$vFieldName];
+
+					if (empty($itemPatch[$vFieldName]) && strlen($itemPatch[$vFieldName]) == 0) {// fix bug in input type date and value="0000-00-00"
+						$value = $this->fixEmptyValueFromUser($kID);
+					}
+					if ($value != $itemOld->$vFieldName) {
+						$itemPatchChecked[$vFieldName] = $value;
+					}
+				}
+			}
+		}
+		if (empty($itemPatchChecked)) {
+			//throw new HttpException("Item Primary Key not set!", 400);
+			return 0;// nothing to change
+		}
+		$itemPatchChecked[$primaryKeyField] = $primaryKey;
+
+		$affected = $ds->updateItem($itemPatchChecked);
+
 		return $affected;
 	}
 
 	public function createItemCopy($item) {
 		$ds = $this->getDataSource();
 		$types = $this->getTypes();
-		$uniqKeys = $this->getUniqueKeys();// TODO: getUniqueFields
-		$primaryKeyField = 'ID';// TODO: getPrimaryField
+		$uniqKeys = $ds->getUniqueKeys();// TODO: getUniqueFields
+		$primaryKeyField = $ds->getPrimaryKeyField();
 		$itemCopy = new stdClass();
 
 		foreach ($types as $kName => $vType) {
@@ -1079,14 +1155,20 @@ class TableAcl {
 	}
 
 	private function _getDataSource($cols) {
-		Lib::loadClass('Data_Source');
-		$dataSource = new Data_Source($this->getDB());
-		$dataSource->setTable($this->getName());
-		$dataSource->setCols($cols);
-		$dataSource->setColTypes($this->getTypes());
-		$dataSource->setVirtualCols($this->getVirtualFieldList());
-		$dataSource->setAccessFltrAllowed(!$this->hasSuperAccessPerms());
-		return $dataSource;
+		Lib::loadClass('DataSourceFactory');
+		$dsConfig = array();
+		$dsConfig['source_id'] = $this->getDB();
+		$dsConfig['object_name'] = $this->getName();
+		$dsConfig['fields'] = $cols;
+		$dsConfig['field_types'] = $this->getTypes();
+		$dsConfig['fields_virtual'] = $this->getVirtualFieldList();
+		$dsConfig['acl_fltr_allowed'] = !$this->hasSuperAccessPerms();
+		return DataSourceFactory::buildFromZasobInfo($dsConfig);
+	}
+
+	public function getPrimaryKeyField() {
+		$ds = $this->getDataSource();
+		return $ds->getPrimaryKeyField();
 	}
 
 }

+ 193 - 196
SE/se-lib/TableAjax.php

@@ -1386,21 +1386,56 @@ class TableAjax extends ViewAjax {
 				frmInlineEdit.on('submit', function() {
 					var data = _inlineEditBox.find('form').serialize();
 					_inlineEditBox.find('.inlineEditBox-cnt').html('<span class="loading-info"> loading ...</span>');
-					jQuery.ajax({
-						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT_INLINE_SAVE',
-						type: 'POST',
-						dataType: 'text',
+					function notifyAjaxCallback(data) {
+						var notify = {};
+						notify.type = (data && data.type)? data.type : '';
+						notify.msg = (data && data.msg)? data.msg : '';
+						switch (notify.type) {
+							case 'success':
+								if (!notify.msg) notify.msg = 'Dane poprawnie zaktualizowane';
+								break;
+							case 'info':
+								if (!notify.msg) notify.msg = 'Nie wprowadzono żadnych zmian';
+								break;
+							case 'error':
+								if (!notify.msg) notify.msg = 'Wystąpiły błędy';
+								break;
+							case 'warning':
+								notify.type = 'warn';
+								if (!notify.msg) notify.msg = 'Wystąpiły błędy';
+								break;
+							default:
+								notify.msg = 'Nieznany błąd';
+								if (data && data.errorCode) notify.msg += ' ' + data.errorCode;
+								notify.type = '';
+						}
+						jQuery.notify(notify.msg, notify.type);
+					}
+
+					$.ajax({
 						data: data,
-						async: true,
-						success: function (data) {
-							//console.log('_inlineEditBox submit ajax data:', data);
-							_inlineEditBox.find('.inlineEditBox-cnt').html(data);
-							_inlineEditBox.find('.btn-save').hide();
-							publ.loadPage(_currPage);
-						},
-						error: function (jhr, textStatus, errorThrown) {
-							if (priv.options.debug) console.log('_inlineEditBox submit ajax error');
+						dataType: 'json',
+						type: "POST",
+						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT_INLINE_SAVE'
+					})
+					.done(function(data, textStatus, jqXHR){
+						notifyAjaxCallback(data);
+						publ.refresh();
+						_inlineEditBox.modal('hide');
+					})
+					.fail(function(jqXHR){// jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
+						if (jqXHR.responseJSON) {
+							notifyAjaxCallback(jqXHR.responseJSON);
 						}
+						else {
+							var txt = jqXHR.responseText || 'Wystąpiły błędy';
+							if (jqXHR.status == 404) {
+								jQuery.notify(jqXHR.responseText, 'error');
+							} else {
+								jQuery.notify(jqXHR.responseText, 'warn');
+							}
+						}
+						_inlineEditBox.modal('hide');
 					});
 
 					return false;
@@ -2434,7 +2469,9 @@ class TableAjax extends ViewAjax {
 		};
 
 		publ.popoverCellRemove = function () {
-			_popoverCellCurrent.popover('destroy');
+			if (_popoverCellCurrent) {
+				_popoverCellCurrent.popover('destroy');
+			}
 			_popoverCell.data('rowid', -1);
 			_popoverCell.data('col', -1);
 			_popoverCell.html('');
@@ -2862,12 +2899,7 @@ function hidePopover() {
 				break;
 			}
 			case 'EDIT_SAVE': {
-				$id = V::get('ID', 0, $_REQUEST, 'int');
-				if ($id > 0) {
-					$this->sendAjaxEditSave($id, $_REQUEST);
-				} else {
-					echo '404';
-				}
+				$this->sendAjaxResponseJson('ajaxEditSave', $_REQUEST);
 				break;
 			}
 			case 'EDIT_INLINE': {
@@ -2881,13 +2913,7 @@ function hidePopover() {
 				break;
 			}
 			case 'EDIT_INLINE_SAVE': {
-				$id = V::get('ID', 0, $_REQUEST, 'int');
-				$col = V::get('col', '', $_REQUEST);
-				if ($id > 0 && !empty($col)) {
-					$this->sendAjaxEditInlineSave($id, $col, $_REQUEST);
-				} else {
-					echo '404';
-				}
+				$this->sendAjaxResponseJson('ajaxEditInlineSave', $_REQUEST);
 				break;
 			}
 			case 'COPY': {
@@ -3080,86 +3106,51 @@ function hidePopover() {
 		exit;
 	}
 
-	private function sendAjaxEditInlineSave($recordId, $fieldName, $args) {
-		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
-		sleep(1);// TODO: RMME DBG loading
-
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: save ID(' . $id . ') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($args);echo'</pre>';}
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">acl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_acl);echo'</pre>';}
-		$dbID = $this->_acl->getDB();
-		$db = DB::getDB($dbID);
-
-		if (!$db) {
-			header('HTTP/1.0 406 Not Acceptable');
-			exit;
+	private function ajaxEditInlineSave($args) {
+		$primaryKeyField = $this->_acl->getPrimaryKeyField();
+		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
+		$fieldName = V::get('col', '', $_REQUEST);
+		if (empty($primaryKey) || empty($fieldName)) {
+			throw new HttpException("Wrong param id or col!", 400);
 		}
 
-		$tblName = $this->_acl->getName();
-
 		$fieldID = $this->_acl->getFieldIdByName($fieldName);
 		if (!$fieldID) {
-			header('HTTP/1.0 404 Not Found');
-			echo "404: No field by name ({$fieldName})";
-			exit;
+			throw new HttpException("Field not exists!", 404);
 		}
 
-		$record = $this->_acl->getItem($recordId);
-		if (!$record) {
-			header('HTTP/1.0 404 Not Found');
-			echo "404: No item ID({$recordId})";
-			exit;
+		$item = $this->_acl->getItem($primaryKey);
+		if (!$item) {
+			throw new HttpException("Item not exists!", 404);
 		}
 
-		if (!$this->_acl->isAllowed($fieldID, 'W', $record)) {
-			header('HTTP/1.0 403 Forbidden');
-			echo "403: field not allowed to Write ({$fieldName})";
-			exit;
-		} else {
-			if ($DBG) echo " Write allowed\n";
+		$itemFromUser = $this->_acl->convertObjectFromUserInput($args, $type = 'array_by_id', $prefix = 'f');
+		if (!isset($itemFromUser[$fieldName])) {
+			throw new HttpException("Field not set!", 400);
 		}
 
-		$sqlObj = new stdClass();
-		if (array_key_exists("f{$fieldID}", $args)) {
-			if (!$this->_acl->isAllowed($fieldID, 'R', $record) && '*****' == $args["f{$fieldID}"]) {
-				// default value for perms 'W' without 'R' is '*****'
-			}
-			else {
-				$sqlObj->{$fieldName} = $args["f{$fieldID}"];
+		$itemPatch = array();
+		$itemPatch[$fieldName] = V::get($fieldName, null, $itemFromUser);
+		$itemPatch[$primaryKeyField] = $primaryKey;
 
-				if (empty($args["f{$fieldID}"]) && strlen($args["f{$fieldID}"]) == 0) {// fix bug in input type date and value="0000-00-00"
-					$sqlObj->{$fieldName} = $this->_acl->fixEmptyValueFromUser($fieldID);
-				}
+		$response = new stdClass();
+		try {
+			$affected = $this->_acl->updateItem($itemPatch);
+
+			if ($affected > 0) {
+				$response->type = 'success';
+				$response->msg = "Rekord zapisany pomyślnie";//"Record saved successfully";
+			} else if ($affected == 0) {
+				$response->type = 'info';
+				$response->msg = "Nie wprowadzono żadnych zmian";
 			}
+			$response->record = $this->_acl->getItem($primaryKey);
 		}
-		else {
-			if ($DBG) echo " TODO: field value not set\n";
-		}
-		$sqlObj->ID = $recordId;
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">E('.$tblName.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}
-
-		$ret = $db->UPDATE_OBJ($tblName, $sqlObj);
-		if ($ret > 0) {
-			echo '<div class="alert alert-success">';
-				echo "Rekord zapisany pomyślnie";//"Record saved successfully";
-			echo '</div>';
-		} else if ($ret == 0) {
-			echo '<div class="alert alert-info">';
-				echo "Nie wprowadzono żadnych zmian";
-				if ($db->has_errors()) {
-					//echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">db errors: (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($db->get_errors());echo'</pre>';
-				}
-			echo '</div>';
-		} else {
-			echo '<div class="alert alert-danger">';
-				echo '<h4>' . "Wystąpiły błędy!" . '</h4>';
-				if ($db->has_errors()) {
-					$errors = $db->get_errors();
-					echo implode('<br>', $errors);
-				}
-			echo '</div>';
+		catch (Exception $e) {
+			$response->type = 'error';
+			$response->msg = $e->getMessage();
 		}
-
-		exit;
+		return $response;
 	}
 
 	private function sendAjaxEdit($id, $args) {
@@ -3289,39 +3280,79 @@ jQuery(document).ready(function(){
 			, alertCnt = jQuery('<div class="container"></div>').prependTo(alertCntWrap);
 		jQuery('<div class="alert alert-danger"><div style="padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;"> zapisywanie ... </div></div>').appendTo(alertCnt);
 
-		jQuery.ajax({
-			url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT_SAVE&ID=<?php echo $record->ID; ?>',
-			type: 'POST',
-			dataType: 'text',
+		function notifyAjaxCallback(data) {
+			var notify = {}, outMsg = '';
+			notify.type = (data && data.type)? data.type : '';
+			notify.msg = (data && data.msg)? data.msg : '';
+			switch (notify.type) {
+				case 'success':
+					if (!notify.msg) notify.msg = 'Dane poprawnie zaktualizowane';
+					break;
+				case 'info':
+					if (!notify.msg) notify.msg = 'Nie wprowadzono żadnych zmian';
+					break;
+				case 'error':
+					if (!notify.msg) notify.msg = 'Wystąpiły błędy';
+					break;
+				case 'warning':
+					notify.type = 'warn';
+					if (!notify.msg) notify.msg = 'Wystąpiły błędy';
+					break;
+				default:
+					notify.msg = 'Nieznany błąd';
+					if (data && data.errorCode) notify.msg += ' ' + data.errorCode;
+					notify.type = '';
+			}
+			jQuery.notify(notify.msg, notify.type);
+			var alertType = ('error' == notify.type) ? 'danger' : notify.type;
+			outMsg = '<div class="alert alert-' + alertType + '">' + notify.msg + '</div>';
+			return outMsg;
+		}
+
+		$.ajax({
 			data: data,
-			async: true,
-			success: function (data) {
+			dataType: 'json',
+			type: "POST",
+			url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT_SAVE&ID=<?php echo $record->ID; ?>',
+		})
+		.done(function(data, textStatus, jqXHR){
+				var outMsg = notifyAjaxCallback(data);
 				alertCntWrap.removeClass('AjaxTable-loading');
 				//console.log('request finished L.<?php echo __LINE__; ?>');
 				alertCnt.empty();
 				var out = '';
-				out += data;
+				out += outMsg;
 				out += '<div class="breadcrumb">' +
 						' <a href="#" onclick="return tableAjaxBackToTable();" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do tabeli <?php echo $this->getLabelHtml(); ?></a>' +
 						' <a href="#EDIT/<?php echo $id; ?>/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-pencil"></i> Edytuj rekord <?php echo $id; ?></a>' +
 					'</div>';
 				jQuery(out).appendTo(alertCnt);
-			},
-			error: function (jhr, textStatus, errorThrown) {
-				var errorTxt = jhr.responseText || 'Error';
-				alertCntWrap.removeClass('AjaxTable-loading');
-				//console.log('Request Error: {0}: {1}'.f(textStatus, errorThrown));
-				alertCnt.empty();
-				jQuery(errorTxt).appendTo(alertCnt);
-				var errLinks = jQuery('<div class="breadcrumb"></div>').appendTo(alertCnt);
-				jQuery('<a href="#" onclick="return tableAjaxBackToTable();"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do tabeli <?php echo $this->getLabelHtml(); ?></a>').appendTo(errLinks);
-				var backToEditBtn = jQuery('<a href="#EDIT/<?php echo $id; ?>/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-pencil"></i> Popraw dane <?php echo $id; ?></a>').appendTo(errLinks);
-				backToEditBtn.on('click', function(){
-					alertCnt.remove();
-					taskCont.children().fadeIn('slow');
-					return false;
-				});
+		})
+		.fail(function(jqXHR){// jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
+			if (jqXHR.responseJSON) {
+				notifyAjaxCallback(jqXHR.responseJSON);
 			}
+			else {
+				var txt = jqXHR.responseText || 'Wystąpiły błędy';
+				if (jqXHR.status == 404) {
+					jQuery.notify(jqXHR.responseText, 'error');
+				} else {
+					jQuery.notify(jqXHR.responseText, 'warn');
+				}
+			}
+
+			alertCntWrap.removeClass('AjaxTable-loading');
+			//console.log('Request Error: {0}: {1}'.f(textStatus, errorThrown));
+			alertCnt.empty();
+			jQuery(errorTxt).appendTo(alertCnt);
+			var errLinks = jQuery('<div class="breadcrumb"></div>').appendTo(alertCnt);
+			jQuery('<a href="#" onclick="return tableAjaxBackToTable();"> <i class="icon-arrow-left"></i> Wróć do tabeli <?php echo $this->getLabelHtml(); ?></a>').appendTo(errLinks);
+			var backToEditBtn = jQuery('<a href="#EDIT/<?php echo $id; ?>/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-small"> <i class="icon-pencil"></i> Popraw dane <?php echo $id; ?></a>').appendTo(errLinks);
+			backToEditBtn.on('click', function(){
+				alertCnt.remove();
+				taskCont.children().fadeIn('slow');
+				return false;
+			});
 		});
 
 		return false;
@@ -3332,7 +3363,7 @@ jQuery(document).ready(function(){
 		input = jQuery(e.target);
 		btn = input.next('.button-appendBack');
 		btnIco = btn.find('.glyphicon');
-console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', btn);
+//console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', btn);
 		if (btn.attr('title') != input.val()) {
 			btnIco.show();
 		} else {
@@ -3344,7 +3375,7 @@ console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', b
 		btn = jQuery(this);
 		btnIco = btn.find('.glyphicon');
 		input = btn.prev();
-console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', btn);
+//console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', btn);
 		if (input.is('input')) {
 			if (btn.attr('title') != input.val()) {
 				input.val(btn.attr('title'));
@@ -3358,77 +3389,40 @@ console.log('title',btn.attr('title'),'val',input.val(),'input', input, 'btn', b
 		exit;
 	}
 
-	private function sendAjaxEditSave($id, $args) {
-		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
-		sleep(1);// TODO: RMME DBG loading
-
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: save ID(' . $id . ') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($args);echo'</pre>';}
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">acl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_acl);echo'</pre>';}
-		$dbID = $this->_acl->getDB();
-		$db = DB::getDB($dbID);
-
-		if (!$db) {
-			header('HTTP/1.0 406 Not Acceptable');
-			exit;
+	private function ajaxEditSave($args) {
+		$primaryKeyField = $this->_acl->getPrimaryKeyField();
+		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
+		if (empty($primaryKey)) {
+			throw new HttpException("Wrong param id!", 400);
 		}
 
-		$tblName = $this->_acl->getName();
-
-		$record = $db->get_by_id($tblName, $id);
-		if (!$this->_acl->canWriteRecord($record) && !$this->_acl->hasPermSuperWrite()) {
-			echo '<div class="alert alert-danger">';
-				echo "Brak dostępu do rekordu";// TODO: more info - reason
-			echo '</div>';
+		$item = $this->_acl->getItem($primaryKey);
+		if (!$item) {
+			throw new HttpException("Item not exists!", 404);
 		}
 
-		$sqlObj = new stdClass();
-		$fields = $this->_acl->getFields();
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fields (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($fields);echo'</pre>';}
-		foreach ($fields as $kID => $vField) {
-			if (!$this->_acl->isAllowed($kID, 'W', $record)) {
-				continue;
-			}
-			if (array_key_exists("f{$kID}", $args)) {
-				if (!$this->_acl->isAllowed($kID, 'R', $record) && '*****' == $args["f{$kID}"]) {
-					// default value for perms 'W' without 'R' is '*****'
-				}
-				else {
-					$value = $args["f{$kID}"];
+		$itemFromUser = $this->_acl->convertObjectFromUserInput($args, $type = 'array_by_id', $prefix = 'f');
 
-					if (empty($args["f{$kID}"]) && strlen($args["f{$kID}"]) == 0) {// fix bug in input type date and value="0000-00-00"
-						$value = $this->_acl->fixEmptyValueFromUser($kID);
-					}
-					if ($record->{$vField['name']} != $value) {
-						$sqlObj->{$vField['name']} = $value;
-					}
-				}
+		$response = new stdClass();
+		try {
+			$itemFromUser[$primaryKeyField] = $primaryKey;
+			$affected = $this->_acl->updateItem($itemFromUser);
+
+			if ($affected > 0) {
+				$response->type = 'success';
+				$response->msg = "Rekord zapisany pomyślnie";//"Record saved successfully";
+			} else if ($affected == 0) {
+				$response->type = 'info';
+				$response->msg = "Nie wprowadzono żadnych zmian";
 			}
+			$response->record = $this->_acl->getItem($primaryKey);
 		}
-		$sqlObj->ID = $id;
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;"> (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}
-
-		$ret = $db->UPDATE_OBJ($tblName, $sqlObj);
-
-		if ($ret > 0) {
-			echo '<div class="alert alert-success">';
-				echo "Rekord zapisany pomyślnie";//"Record saved successfully";
-			echo '</div>';
-		} else if ($ret == 0) {
-			echo '<div class="alert alert-info">';
-				echo "Nie wprowadzono żadnych zmian";
-			echo '</div>';
-		} else {
-			header('HTTP/1.0 404 Not Found');
-			echo '<div class="alert alert-danger">';
-				echo '<h4>' . "Wystąpiły błędy!" . '</h4>';
-				if ($db->has_errors()) {
-					$errors = $db->get_errors();
-					echo implode('<br>', $errors);
-				}
-			echo '</div>';
+		catch (Exception $e) {
+			$response->type = 'error';
+			$response->msg = "Wystąpiły błędy!";
+			$response->msg .= $e->getMessage();
 		}
-
-		exit;
+		return $response;
 	}
 
 	private function sendAjaxCreate($args) {
@@ -5090,15 +5084,17 @@ jQuery(document).ready(function(){
 	}
 
 	private function ajaxTheGeomSave($args) {
-		$id = V::get('ID', 0, $args, 'int');
+		$primaryKeyField = $this->_acl->getPrimaryKeyField();
+		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
 		$polygon = V::get('polygon', 0, $args);
 		$geomFieldName = 'the_geom';
-		if ($id <= 0) {
+
+		if ($primaryKey <= 0) {
 			throw new HttpException("Wrong param ID", 404);
 		}
 		// TODO: validate polygon - ex.: POLYGON((2072030.2315435 7234115.910678,2072029.4815435 7234093.660678,2072115.2315435 7234091.160678,2072115.4815435 7234113.660678,2072115.2315435 7234113.660678,2072094.2315435 7234113.910678,2072030.2315435 7234115.910678)))
 
-		$record = $this->_acl->getItem($id);
+		$record = $this->_acl->getItem($primaryKey);
 		if (!$this->_acl->canWriteRecord($record) && !$this->_acl->hasPermSuperWrite()) {
 			throw new HttpException("Brak dostępu do rekordu", 403);
 		}
@@ -5108,9 +5104,9 @@ jQuery(document).ready(function(){
 			throw new HttpException("Brak dostępu do zapisu dla pola {$geomFieldName}", 403);
 		}
 
-		$itemPatch = new stdClass();
-		$itemPatch->{$geomFieldName} = "GeomFromText('{$polygon}')";
-		$itemPatch->ID = $id;
+		$itemPatch = array();
+		$itemPatch[$geomFieldName] = "GeomFromText('{$polygon}')";
+		$itemPatch[$primaryKeyField] = $primaryKey;
 
 		$response = new stdClass();
 		try {
@@ -5123,7 +5119,7 @@ jQuery(document).ready(function(){
 				$response->type = 'info';
 				$response->msg = "Nie wprowadzono żadnych zmian";
 			}
-			$response->record = $this->_acl->getItem($id);
+			$response->record = $this->_acl->getItem($primaryKey);
 		}
 		catch (Exception $e) {
 			$response->type = 'error';
@@ -5159,20 +5155,21 @@ jQuery(document).ready(function(){
 	}
 
 	private function ajaxTheGeomRemove($args) {// ajax task 'THE_GEOM_REMOVE'
-		$id = V::get('ID', 0, $args, 'int');
+		$primaryKeyField = $this->_acl->getPrimaryKeyField();
+		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
 		$geomFieldName = 'the_geom';
 		$response = new stdClass();
 
-		if ($id <= 0) {
+		if ($primaryKey <= 0) {
 			throw new HttpException("Wrong param ID", 404);
 		}
 
-		$record = $this->_acl->getItem($id);
+		$record = $this->_acl->getItem($primaryKey);
 		if (!$record) {
-			throw new HttpException("Nie odnaleziono rekordu nr {$id}", 404);
+			throw new HttpException("Nie odnaleziono rekordu nr {$primaryKey}", 404);
 		}
 		if (!$this->_acl->canWriteRecord($record) && !$this->_acl->hasPermSuperWrite()) {
-			throw new HttpException("Brak dostępu do rekordu nr {$id}", 403);
+			throw new HttpException("Brak dostępu do rekordu nr {$primaryKey}", 403);
 		}
 
 		$theGeomFieldId = $this->_acl->getFieldIdByName($geomFieldName);
@@ -5187,9 +5184,9 @@ jQuery(document).ready(function(){
 			return $response;
 		}
 
-		$itemPatch = new stdClass();
-		$itemPatch->{$geomFieldName} = "NULL";
-		$itemPatch->ID = $id;
+		$itemPatch = array();
+		$itemPatch[$geomFieldName] = "NULL";
+		$itemPatch[$primaryKeyField] = $primaryKey;
 
 		$response = new stdClass();
 		try {
@@ -5202,7 +5199,7 @@ jQuery(document).ready(function(){
 				$response->type = 'info';
 				$response->msg = "Nie wprowadzono żadnych zmian";
 			}
-			$response->record = $this->_acl->getItem($id);
+			$response->record = $this->_acl->getItem($primaryKey);
 		}
 		catch (Exception $e) {
 			$response->type = 'error';