Bladeren bron

Merge branch 'master' of ssh://biuro.biall-net.pl:2222/plabudda/se

Mariusz Muszyński 8 jaren geleden
bovenliggende
commit
3d6160edae

+ 2 - 0
SE/schema/WPS_Functions/folders_interface/build_folders_interface.xml

@@ -48,6 +48,8 @@
       <echo>#46 test if ok $p5build_SE.build_procesy5_config.folders.root_points.mount_point = ${p5build_SE.build_procesy5_config.folders.root_points.mount_point}
          $p5xmli.NamespaceObjectInstanceTable.local = ${p5xmli.NamespaceObjectInstanceTable.local}
          $p5xmli.TypeName.local = ${p5xmli.TypeName.local}
+         $p5fi.primaryKey.local = ${p5fi.primaryKey.local}
+         $p5fi.primaryKeyField.local = ${p5fi.primaryKeyField.local}
          $p5xmli.NamespaceDatabaseStorageDefinitionOptSuffix = ${p5xmli.NamespaceDatabaseStorageDefinitionOptSuffix}
          test eval p5build_SE.build_procesy5_config.folders.IN7_DZIENNIK_KORESP_COLUMN.mount_point = ${p5build_SE.build_procesy5_config.folders.IN7_DZIENNIK_KORESP_COLUMN.mount_point}
       </echo>

+ 16 - 1
SE/se-lib/AclQueryFeatures.php

@@ -329,7 +329,15 @@ class AclQueryFeatures {
 					case 'Status': $this->_query->where($this->parseSpecialFilterStatus($v)); break;
 					case 'Spotkania': $this->_query->where($this->parseSpecialFilterSpotkania($v)); break;
 					case 'Access': break; // SKIP - used below
-					default: throw new Exception("Not Implemented special filter '".substr($k, 3)."'");
+					default: {
+						if (method_exists($this->_acl, 'parseSpecialFilter')) {
+							$this->_query->where(
+								$this->_acl->parseSpecialFilter(substr($k, 3), $v)
+							);
+						} else {
+							throw new Exception("Not Implemented special filter '".substr($k, 3)."'");
+						}
+					}
 				}
 			} else if ('ogc:Filter' === $k) {
 				$this->_query->where($this->parseOgcFilter($v));
@@ -468,6 +476,7 @@ class AclQueryFeatures {
 	}
 
 	public function getTotal() {
+		$this->_beforeFetchData();
 		if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
 		if (null !== $this->_total) return $this->_total;
 		$this->_total = $this->getQuery()->fetchTotal();
@@ -479,6 +488,7 @@ class AclQueryFeatures {
 
 	public function getItems() {
 		$this->_logToFile('getItems() ...');
+		$this->_beforeFetchData();
 		if ($this->_legacyMode) return $this->_acl->getItems($this->_params);// TODO: array_map( $r => (array)$r )
 		// 'limit' => 10,
 		//	'limitstart' => 0,
@@ -518,7 +528,12 @@ class AclQueryFeatures {
 		return $items;
 	}
 
+	function _beforeFetchData() {
+		if (method_exists($this->_acl, 'onBeforeFetchData')) $this->_acl->onBeforeFetchData();
+	}
+
 	public function getItem($primaryKey) { // TODO: throw exception if not found?
+		$this->_beforeFetchData();
 		if ($this->_legacyMode) return (array)$this->_acl->getItem($primaryKey, $this->_params);
 		$select = $this->prepareSelect();
 		$pkField = $this->_acl->getPrimaryKeyField();

+ 25 - 12
SE/se-lib/Core/AclHelper.php

@@ -83,19 +83,32 @@ class Core_AclHelper {// Helper class for Acl
 			}
 			DBG::log($objItem, 'array', "DBG objItem({$namespace})");
 			if (!$objItem['idZasob']) throw new Exception("Missing idZasob for namespace '{$namespace}'");
-			if (!in_array($objItem['_type'], [
-				// 'TableAcl', // TODO: TEST - to replace TableAcl by AntAcl or use object with namespace + '/tableName'?
-				'AntAcl',
-			])) throw new Exception("Not Implemented acl type '{$objItem['_type']}'");
-			if (!$objItem['isObjectActive']) {
-				if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
-				if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
-				throw new Exception("namespace is not activated '{$namespace}'");
-			}
+			switch ($objItem['_type']) {
+				// case 'TableAcl': // TODO: TEST - to replace TableAcl by AntAcl or use object with namespace + '/tableName'?
+				case 'AntAcl': {
+					if (!$objItem['isObjectActive']) {
+						if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
+						if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
+						throw new Exception("namespace is not activated '{$namespace}'");
+					}
 
-			Lib::loadClass('AntAclBase');
-			$acl = AntAclBase::buildInstance($objItem['idZasob'], $objItem);
-			return $acl;
+					Lib::loadClass('AntAclBase');
+					$acl = AntAclBase::buildInstance($objItem['idZasob'], $objItem);
+					return $acl;
+				} break;
+				case 'StorageAcl': {
+					if (!$objItem['isObjectActive']) {
+						if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
+						if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
+						throw new Exception("namespace is not activated '{$namespace}'");
+					}
+
+					Lib::loadClass('StorageAclBase');
+					$acl = StorageAclBase::buildInstance($objItem['idZasob'], $objItem);
+					return $acl;
+				} break;
+				default: throw new Exception("Not Implemented acl type '{$objItem['_type']}'");
+			}
 		} catch (Exception $e) {
 			DBG::log($e);
 		}

+ 1 - 2
SE/se-lib/Core/AclSimpleSchemaBase.php

@@ -378,8 +378,7 @@ class Core_AclSimpleSchemaBase extends Core_AclBase {
             //if($itemFieldName=='LINK_PROBLEMS')
             //	$link = str_replace("{{$alias}}", $item[$itemFieldName],   str_replace($link,'"','test')   ); // bledy z cudzyslowami w <a href=\"mailto:?
             //else //to nie tutaj - ciezko wysledzic
-            $link = str_replace("{{$alias}}", $item[$itemFieldName],   $link  ); 
-
+            $link = str_replace("{{$alias}}", $item[$itemFieldName], $link);
           }
           $item[$fieldName] = $link;
         } break;

+ 6 - 0
SE/se-lib/Core/Pdo.php

@@ -450,6 +450,12 @@ EOF_STRUCT_MYSQL;
 		}, $this->fetchAll($sql, $values));
 	}
 
+	public function fetchValuesListByKey($sql, $key, $values = []) { // for sql like `select ID from ...` @returns array of ID
+		return array_map(function ($row) {
+			return reset($row);
+		}, $this->fetchAllByKey($sql, $key, $values));
+	}
+
 	public function fetchFirst($sql, $values = []) { // fetch only first row
 		$sth = $this->prepare($sql);
 		if (!empty($values)) {

+ 1 - 0
SE/se-lib/DBG.php

@@ -256,6 +256,7 @@ class DBG {
 	public static function log($mixedArg, $type = '', $msg = '') {
 		if (!self::isActive() && !self::isLogActiveByAdmin()) return;
 		if ('Debug' == V::get('_route', '', $_REQUEST) && !V::get('DBG', '', $_REQUEST)) return;
+		if ('UrlAction_Bocian' == V::get('_route', '', $_REQUEST) && 'fetchProgressAjax' == V::get('_task', '', $_REQUEST) && !V::get('DBG', '', $_REQUEST)) return;
 		static $_firstRun = true;
 		if ($_firstRun) {
 			$_firstRun = false;

+ 1 - 1
SE/se-lib/InstanceConfig.php

@@ -238,7 +238,7 @@ class InstanceConfig {
 
 	static function generateIsInstanceView($namespace, $item = null, Type_InstanceConfig $conf = null) {
 		if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
-		if (!in_array( $item['_type'], [ 'AntAcl' ] )) return null;
+		if (!in_array( $item['_type'], [ 'AntAcl', 'StorageAcl' ] )) return null;
 		$idInstance = ($conf) ? $conf->id : self::getInstanceId($namespace);
 
 		$localFieldsWithRestrictions = array_filter($item['field'], function ($field) {

+ 11 - 1
SE/se-lib/ProcesMenu.php

@@ -964,9 +964,17 @@ jQuery(document).ready(function() {
 			(function (global) {
 				if (!global.p5UI__MenuStore) throw 'Missing global.p5UI__MenuStore'
 
+				function _changeIconStarToLoading(node) {
+					if (!node.classList.contains('glyphicon-star') && !node.classList.contains('glyphicon-star-empty')) return;
+					node.classList.remove('glyphicon-star', 'glyphicon-star-empty')
+					node.classList.add('glyphicon-refresh')
+				}
+
 				function p5BookmarksAdd(e, id) {
 					e.preventDefault()
 					e.stopPropagation()
+					if (e.target.classList.contains('glyphicon-refresh')) return;
+					_changeIconStarToLoading(e.target)
 					global.p5UI__MenuStore.remoteUpdate({
 						'_postTask': 'addBookmark',
 						'_zasobID': id,
@@ -975,6 +983,8 @@ jQuery(document).ready(function() {
 				function p5BookmarksRemove(e, id) {
 					e.preventDefault()
 					e.stopPropagation()
+					if (e.target.classList.contains('glyphicon-refresh')) return;
+					_changeIconStarToLoading(e.target)
 					global.p5UI__MenuStore.remoteUpdate({
 						'_postTask': 'removeBookmark',
 						'_zasobID': id,
@@ -987,7 +997,7 @@ jQuery(document).ready(function() {
 		");
 
 		UI::inlineCSS(APP_PATH_WWW . '/static/p5UI/initP5MainMenuDropdown.css');
-		UI::inlineJS(APP_PATH_WWW . '/static/p5UI/initP5MainMenuDropdown.js');
+		UI::inlineJS(APP_PATH_WWW . '/static/p5UI/initP5MainMenuDropdown.js', [ 'DBG' => 0 ]);
 
 		echo UI::h('script', [], "
 			(function (global) {

+ 156 - 173
SE/se-lib/Przypomnij.php

@@ -32,8 +32,6 @@ class Przypomnij {
 	}
 
 	public function fetchData($usrLogin = null) {
-		$db = DB::getDB();
-
 		if (!$usrLogin) {
 			$usrLogin = User::getLogin();
 			$usrAclGroups = User::getLdapGroupsNames();
@@ -58,7 +56,8 @@ class Przypomnij {
 					or kor.`L_APPOITMENT_USER`='{$usrLogin}'
 					)
 		";
-		$sql = "select kor.ID
+		$sql = "
+			select kor.ID
 				, kor.ID_PROJECT
 				, kor.A_STATUS
 				, kor.L_APPOITMENT_USER
@@ -69,30 +68,30 @@ class Przypomnij {
 				, kor.A_RECORD_UPDATE_DATE
 				, kor.A_RECORD_UPDATE_AUTHOR
 				, kor.K_TYP_KORESP, kor.K_TYP_RODZAJ
-				-- ? , kor.K_TYP_RODZAJ_OPIS
+				-- , kor.K_TYP_RODZAJ_OPIS
 				, kor.K_OD_KOGO
 				, kor.OD_KOGO_ADRES
 				, kor.K_ZAWARTOS
-				-- TODO:	, proj.M_DIST_DESC
+				-- , proj.M_DIST_DESC
 				, kor.K_LOKALIZACJA
 				, kor.K_LOKALIZACJA_OPIS
 			from `IN7_DZIENNIK_KORESP` as kor
 			where kor.`A_STATUS` not in ('OFF_HARD','DELETED')
 				{$sqlAclFltrKoresp}
 		";
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			$r->A_RECORD_CREATE_DATE = $this->fixDateFormat($r->A_RECORD_CREATE_DATE);
-			$r->A_RECORD_UPDATE_DATE = $this->fixDateFormat($r->A_RECORD_UPDATE_DATE);
-			$r->_task_type = 'koresp';
-			$r->_show = false;
-			$r->_acl_proj_id = (int)$r->ID_PROJECT;
-			$r->_title = "<strong>{$r->K_OD_KOGO}</strong><br><em>{$r->OD_KOGO_ADRES}</em><br>{$r->K_ZAWARTOS}";
-			$r->_type = "{$r->K_TYP_KORESP}-{$r->K_TYP_RODZAJ}";
-			$r->_l_app = $r->L_APPOITMENT_USER;
-			$r->_l_app_date = $r->L_APPOITMENT_DATE;
-			$this->_data['koresp'][$r->ID] = $r;
-		}
+		$this->_data['koresp'] = array_map(function ($row) {
+			return (object)array_merge($row, [
+				'A_RECORD_CREATE_DATE' => $this->fixDateFormat($row['A_RECORD_CREATE_DATE']),
+				'A_RECORD_UPDATE_DATE' => $this->fixDateFormat($row['A_RECORD_UPDATE_DATE']),
+				'_task_type' => 'koresp',
+				'_show' => false,
+				'_acl_proj_id' => (int)$row['ID_PROJECT'],
+				'_title' => "<strong>{$row['K_OD_KOGO']}</strong><br><em>{$row['OD_KOGO_ADRES']}</em><br>{$row['K_ZAWARTOS']}",
+				'_type' => "{$row['K_TYP_KORESP']}-{$row['K_TYP_RODZAJ']}",
+				'_l_app' => $row['L_APPOITMENT_USER'],
+				'_l_app_date' => $row['L_APPOITMENT_DATE'],
+			]);
+		}, DB::getPDO()->fetchAllByKey($sql, $key = 'ID'));
 
 		$sqlAclFltrProj = "
 			and (proj.`A_ADM_COMPANY` in({$sqlUsrAclGroups}, '')
@@ -100,7 +99,8 @@ class Przypomnij {
 					or proj.`L_APPOITMENT_USER`='{$usrLogin}'
 					)
 		";
-		$sql = "select proj.ID
+		$sql = "
+			select proj.ID
 				, proj.P_ID
 				, proj.A_STATUS
 				, proj.M_DIST_DESC
@@ -120,20 +120,20 @@ class Przypomnij {
 			where proj.`A_STATUS` NOT IN ('OFF_HARD','DELETED')
 				{$sqlAclFltrProj}
 		";
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			$r->A_RECORD_CREATE_DATE = $this->fixDateFormat($r->A_RECORD_CREATE_DATE);
-			$r->A_RECORD_UPDATE_DATE = $this->fixDateFormat($r->A_RECORD_UPDATE_DATE);
-			$r->M_DIST_DESC = htmlspecialchars($r->M_DIST_DESC);// TODO: fix bug in html a href inside M_DIST_DESC
-			$r->_task_type = 'projekt';
-			$r->_show = false;
-			$r->_acl_proj_id = (int)$r->P_ID;
-			$r->_title = $r->M_DIST_DESC;
-			$r->_type = $r->M_DIST_TYPE;
-			$r->_l_app = $r->L_APPOITMENT_USER;
-			$r->_l_app_date = $r->L_APPOITMENT_DATE;
-			$this->_data['projekt'][$r->ID] = $r;
-		}
+		$this->_data['projekt'] = array_map(function ($row) {
+			return (object)array_merge($row, [
+				'A_RECORD_CREATE_DATE' => $this->fixDateFormat($row['A_RECORD_CREATE_DATE']),
+				'A_RECORD_UPDATE_DATE' => $this->fixDateFormat($row['A_RECORD_UPDATE_DATE']),
+				'M_DIST_DESC' => htmlspecialchars($row['M_DIST_DESC']), // TODO: fix bug in html a href inside M_DIST_DES
+				'_task_type' => 'projekt',
+				'_show' => false,
+				'_acl_proj_id' => (int)$row['P_ID'],
+				'_title' => $row['M_DIST_DESC'],
+				'_type' => $row['M_DIST_TYPE'],
+				'_l_app' => $row['L_APPOITMENT_USER'],
+				'_l_app_date' => $row['L_APPOITMENT_DATE'],
+			]);
+		}, DB::getPDO()->fetchAllByKey($sql, $key = 'ID'));
 
 		$sqlAclFltrProces = "
 			and (p.`A_ADM_COMPANY` in({$sqlUsrAclGroups}, '')
@@ -141,7 +141,8 @@ class Przypomnij {
 					or p.`L_APPOITMENT_USER`='{$usrLogin}'
 					)
 		";
-		$sql = "select p.ID
+		$sql = "
+			select p.ID
 				, p.`DESC`
 				, p.`A_STATUS`
 				, p.`TYPE`
@@ -159,18 +160,18 @@ class Przypomnij {
 				and p.`L_APPOITMENT_USER`!=''
 				{$sqlAclFltrProces}
 		";
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			$r->A_RECORD_CREATE_DATE = $this->fixDateFormat($r->A_RECORD_CREATE_DATE);
-			$r->A_RECORD_UPDATE_DATE = $this->fixDateFormat($r->A_RECORD_UPDATE_DATE);
-			$r->_task_type = 'proces';
-			$r->_show = false;
-			$r->_title = $r->DESC;
-			$r->_type = $r->TYPE;
-			$r->_l_app = $r->L_APPOITMENT_USER;
-			$r->_l_app_date = $r->L_APPOITMENT_DATE;
-			$this->_data['proces'][$r->ID] = $r;
-		}
+		$this->_data['proces'] = array_map(function ($row) {
+			return (object)array_merge($row, [
+				'A_RECORD_CREATE_DATE' => $this->fixDateFormat($row['A_RECORD_CREATE_DATE']),
+				'A_RECORD_UPDATE_DATE' => $this->fixDateFormat($row['A_RECORD_UPDATE_DATE']),
+				'_task_type' => 'proces',
+				'_show' => false,
+				'_title' => $row['DESC'],
+				'_type' => $row['TYPE'],
+				'_l_app' => $row['L_APPOITMENT_USER'],
+				'_l_app_date' => $row['L_APPOITMENT_DATE'],
+			]);
+		}, DB::getPDO()->fetchAllByKey($sql, $key = 'ID'));
 
 		$sqlAclFltrProblems = "
 			and (probl.`A_ADM_COMPANY` in({$sqlUsrAclGroups}, '')
@@ -178,7 +179,8 @@ class Przypomnij {
 					or probl.`L_APPOITMENT_USER`='{$usrLogin}'
 					)
 		";
-		$sql = "select probl.ID
+		$sql = "
+			select probl.ID
 				, probl.PARENT_ID
 				, probl.A_STATUS
 				, probl.L_APPOITMENT_DATE
@@ -196,19 +198,19 @@ class Przypomnij {
 				and probl.`L_APPOITMENT_USER`!=''
 				{$sqlAclFltrProblems}
 		";
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			$r->A_RECORD_CREATE_DATE = $this->fixDateFormat($r->A_RECORD_CREATE_DATE);
-			$r->A_RECORD_UPDATE_DATE = $this->fixDateFormat($r->A_RECORD_UPDATE_DATE);
-			$r->A_PROBLEM_DESC = htmlspecialchars($r->A_PROBLEM_DESC);
-			$r->_task_type = 'task';
-			$r->_show = false;
-			$r->_title = $r->A_PROBLEM_DESC;
-			$r->_type = $r->M_DIST_TYPE;
-			$r->_l_app = $r->L_APPOITMENT_USER;
-			$r->_l_app_date = $r->L_APPOITMENT_DATE;
-			$this->_data['task'][$r->ID] = $r;
-		}
+		$this->_data['task'] = array_map(function ($row) {
+			return (object)array_merge($row, [
+				'A_RECORD_CREATE_DATE' => $this->fixDateFormat($row['A_RECORD_CREATE_DATE']),
+				'A_RECORD_UPDATE_DATE' => $this->fixDateFormat($row['A_RECORD_UPDATE_DATE']),
+				'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+				'_task_type' => 'task',
+				'_show' => false,
+				'_title' => $row['A_PROBLEM_DESC'],
+				'_type' => $row['M_DIST_TYPE'],
+				'_l_app' => $row['L_APPOITMENT_USER'],
+				'_l_app_date' => $row['L_APPOITMENT_DATE'],
+			]);
+		}, DB::getPDO()->fetchAllByKey($sql, $key = 'ID'));
 
 		$sqlAclFltrProces = "
 			and (p.`A_ADM_COMPANY` in({$sqlUsrAclGroups}, '')
@@ -216,7 +218,8 @@ class Przypomnij {
 					or p.`L_APPOITMENT_USER`='{$usrLogin}'
 					)
 		";
-		$sql = "select p.ID
+		$sql = "
+			select p.ID
 				, p.`DESC`
 				, p.`A_STATUS`
 				, p.`TYPE`
@@ -233,19 +236,20 @@ class Przypomnij {
 				and p.`L_APPOITMENT_USER`!=''
 				{$sqlAclFltrProces}
 		";
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			$r->A_RECORD_CREATE_DATE = $this->fixDateFormat($r->A_RECORD_CREATE_DATE);
-			$r->A_RECORD_UPDATE_DATE = $this->fixDateFormat($r->A_RECORD_UPDATE_DATE);
-			$r->_task_type = 'zasob';
-			$r->_show = false;
-			$r->_title = $r->DESC;
-			$r->_type = $r->TYPE;
-			$r->_l_app = $r->L_APPOITMENT_USER;
-			$r->_l_app_date = $r->L_APPOITMENT_DATE;
-			$this->_data['zasob'][$r->ID] = $r;
-		}
-
+		$this->_data['zasob'] = array_map(function ($row) {
+			return (object)array_merge($row, [
+				'A_RECORD_CREATE_DATE' => $this->fixDateFormat($row['A_RECORD_CREATE_DATE']),
+				'A_RECORD_UPDATE_DATE' => $this->fixDateFormat($row['A_RECORD_UPDATE_DATE']),
+				'_task_type' => 'zasob',
+				'_show' => false,
+				'_title' => $row['DESC'],
+				'_type' => $row['TYPE'],
+				'_l_app' => $row['L_APPOITMENT_USER'],
+				'_l_app_date' => $row['L_APPOITMENT_DATE'],
+			]);
+		}, DB::getPDO()->fetchAllByKey($sql, $key = 'ID'));
+
+		DBG::log($this->_data, 'array', "DBG: Przypomnij->_data");
 		$this->_fetchLAppUsers();
 	}
 
@@ -273,7 +277,6 @@ class Przypomnij {
 	}
 
 	private function _fetchLAppUsers() {
-		$this->_createCacheTable();
 		$this->_updateCacheTable();
 
 		$projTodo = array();
@@ -296,46 +299,44 @@ class Przypomnij {
 			return;
 		}
 
-		$db = DB::getDB();
 		$projIds = array_keys($projTodo);
-		$sql = "select c.`ID`, c.`ID_PROJECT`, c.`L_APPOITMENT_USER`, c.`L_APPOITMENT_DATE`
-			from `_PRZYPOMNIJ_CACHE` as c
-			where c.`ID_PROJECT` in(" . implode(",", $projIds) . ")
-		";
-if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">sql (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sql);echo'</pre>';}
-		$res = $db->query($sql);
-		if (!$res) {
-			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>';
-			}
-		}
-		while ($r = $db->fetch($res)) {
-			foreach ($projTodo[$r->ID_PROJECT] as $kRowId => $vType) {
-				if ($vType == 'projekt') {
-					if (isset($this->_data['projekt'][$kRowId])) {
-						if (!empty($r->L_APPOITMENT_USER)) {
-							$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+		try {
+			$toUpdateByCache = DB::getPDO()->fetchAll("
+				select c.`ID`, c.`ID_PROJECT`, c.`L_APPOITMENT_USER`, c.`L_APPOITMENT_DATE`
+				from `_PRZYPOMNIJ_CACHE` as c
+				where c.`ID_PROJECT` in(" . implode(",", $projIds) . ")
+			");
+			foreach ($toUpdateByCache as $row) {
+				$r = (object)$row;
+				foreach ($projTodo[$r->ID_PROJECT] as $kRowId => $vType) {
+					if ($vType == 'projekt') {
+						if (isset($this->_data['projekt'][$kRowId])) {
+							if (!empty($r->L_APPOITMENT_USER)) {
+								$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+							}
+						} else {
+							//echo '<p style="color:red">'."Error not set \$this->_data['projekt'][$kRowId]".'</p>';
 						}
-					} else {
-						//echo '<p style="color:red">'."Error not set \$this->_data['projekt'][$kRowId]".'</p>';
 					}
-				}
-				else if ($vType == 'koresp') {
-					if (!empty($r->L_APPOITMENT_USER)) {
-						$this->_data['koresp'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
-						if (!empty($r->L_APPOITMENT_DATE) && empty($this->_data['koresp'][$kRowId]->_l_app_date)) {
-							//$this->_data['koresp'][$kRowId]->_l_app_date = $r->L_APPOITMENT_DATE;
-							$this->_data['koresp'][$kRowId]->_l_app_date = '0000-00-00';
+					else if ($vType == 'koresp') {
+						if (!empty($r->L_APPOITMENT_USER)) {
+							$this->_data['koresp'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+							if (!empty($r->L_APPOITMENT_DATE) && empty($this->_data['koresp'][$kRowId]->_l_app_date)) {
+								//$this->_data['koresp'][$kRowId]->_l_app_date = $r->L_APPOITMENT_DATE;
+								$this->_data['koresp'][$kRowId]->_l_app_date = '0000-00-00';
+							}
 						}
 					}
 				}
 			}
+		} catch (Exception $e) {
+			DBG::log($e);
+			UI::alert('danger', $e->getMessage());
 		}
 	}
 
 	private function _fetchLAppUsersRec() {
 		$projTodo = array();
-		$db = DB::getDB();
 		$loopLimit = $this->_deepRecurseLimit;
 		$firstLoop = true;
 		do {
@@ -385,58 +386,52 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 			if ($firstLoop) $firstLoop = false;
 
 			if (empty($projTodo)) {
-				if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">loop(' . ($this->_deepRecurseLimit - $loopLimit) . ')  (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("break loop " . ($this->_deepRecurseLimit - $loopLimit));echo'</pre>';}
 				break;
 			}
-			if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">loop(' . ($this->_deepRecurseLimit - $loopLimit) . ') $projTodo (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($projTodo);echo'</pre>';}
 			$projIds = array_keys($projTodo);
-			$sql = "select `ID`, `P_ID`, `L_APPOITMENT_USER`, `L_APPOITMENT_USER`
-				from `IN7_MK_BAZA_DYSTRYBUCJI`
-				where `ID` in(" . implode(",", $projIds) . ")
-			";
-			if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">loop(' . ($this->_deepRecurseLimit - $loopLimit) . ') sql (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sql);echo'</pre>';}
-			$res = $db->query($sql);
-			if (!$res) {
-				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>';
-				}
-			}
-			while ($r = $db->fetch($res)) {
-				//if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">loop(' . ($this->_deepRecurseLimit - $loopLimit) . ') r [isset($projTodo[$r->ID]) = '.isset($projTodo[$r->ID]).'] (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($r);echo'</pre>';}
-				foreach ($projTodo[$r->ID] as $kRowId => $vType) {
-					if ($vType == 'projekt') {
-						if (isset($this->_data['projekt'][$kRowId])) {
-							$this->_data['projekt'][$kRowId]->_acl_proj_id = (int)$r->P_ID;
+			try {
+				$toUpdateByCache = DB::getPDO()->fetchAll("
+					select `ID`, `P_ID`, `L_APPOITMENT_USER`, `L_APPOITMENT_USER`
+					from `IN7_MK_BAZA_DYSTRYBUCJI`
+					where `ID` in(" . implode(",", $projIds) . ")
+				");
+				foreach ($toUpdateByCache as $row) {
+					$r = (object)$row;
+					foreach ($projTodo[$r->ID] as $kRowId => $vType) {
+						if ($vType == 'projekt') {
+							if (isset($this->_data['projekt'][$kRowId])) {
+								$this->_data['projekt'][$kRowId]->_acl_proj_id = (int)$r->P_ID;
+								if (!empty($r->L_APPOITMENT_USER)) {
+									$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+									//if (!empty($r->L_APPOINTMENT_DATE)) {
+									//	$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+									//}
+								}
+							} else {
+								echo '<p style="color:red">'."Error not set \$this->_data['projekt'][$kRowId]".'</p>';
+							}
+						} else if ($vType == 'koresp') {
+							$this->_data['koresp'][$kRowId]->_acl_proj_id = (int)$r->P_ID;
 							if (!empty($r->L_APPOITMENT_USER)) {
-								$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
+								$this->_data['koresp'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
 								//if (!empty($r->L_APPOINTMENT_DATE)) {
 								//	$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
 								//}
 							}
-						} else {
-							echo '<p style="color:red">'."Error not set \$this->_data['projekt'][$kRowId]".'</p>';
-						}
-					} else if ($vType == 'koresp') {
-						$this->_data['koresp'][$kRowId]->_acl_proj_id = (int)$r->P_ID;
-						if (!empty($r->L_APPOITMENT_USER)) {
-							$this->_data['koresp'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
-							//if (!empty($r->L_APPOINTMENT_DATE)) {
-							//	$this->_data['projekt'][$kRowId]->_l_app = $r->L_APPOITMENT_USER;
-							//}
 						}
 					}
-				}
 
-				unset($projTodo[$r->ID]);
+					unset($projTodo[$r->ID]);
+				}
+			} catch (Exception $e) {
+				DBG::log($e);
+				UI::alert('danger', $e->getMessage());
 			}
-if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">loop(' . ($this->_deepRecurseLimit - $loopLimit) . ') this->_data (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_data);echo'</pre>';}
-
 		} while (--$loopLimit);
 	}
 
 	public function getAllowedUsersList() {
 		$allowedUsers = array();
-		$db = DB::getDB();
 
 		$usrLogin = User::getLogin();
 		$usrAclGroups = User::getLdapGroupsNames();
@@ -473,17 +468,16 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 						}
 						$sqlAclFltr = " and {$sqlFltr}";
 					}
-					$sql = "select distinct t.`L_APPOITMENT_USER`
+					$sql = "
+						select distinct t.`L_APPOITMENT_USER`
 						from `{$tblName}` t
 						where t.`L_APPOITMENT_USER`!=''
 							and t.`A_STATUS` not in ('OFF_HARD','DELETED')
 							{$sqlAclFltr}
 					";
-					$res = $db->query($sql) or die("blad zapytania do bazy {$sql}");
-					while ($r = $db->fetch($res)) {
-						$allowedUsers[$r->L_APPOITMENT_USER] = true;
+					foreach (DB::getPDO()->fetchValuesList($sql) as $userLogin) {
+						$allowedUsers[$userLogin] = true;
 					}
-					DBG::_('DBG_P', '>2', 'allowedUsers after '.$tblName.'', implode(',', array_keys($allowedUsers)), __CLASS__, __FUNCTION__, __LINE__);
 				}
 			}
 		}
@@ -518,15 +512,14 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 			}
 			$sqlFoundUsers = array_keys($allowedUsers);
 			$sqlFoundUsers = "'" . implode("','", $sqlFoundUsers) . "'";
-			$sql = "select t.`ADM_ACCOUNT`
+			$sql = "
+				select t.`ADM_ACCOUNT`
 				from `{$tblName}` t
 				where t.`ADM_ACCOUNT` in({$sqlFoundUsers})
 					{$sqlAclFltr}
 			";
-			DBG::_('DBG_P', '>2', 'sql', $sql, __CLASS__, __FUNCTION__, __LINE__);
-			$res = $db->query($sql) or die("blad zapytania do bazy {$sql}");
-			while ($r = $db->fetch($res)) {
-				$allowedUsersFiltered[$r->ADM_ACCOUNT] = true;
+			foreach (DB::getPDO()->fetchValuesList($sql) as $userLogin) {
+				$allowedUsersFiltered[$userLogin] = true;
 			}
 			$allowedUsers = $allowedUsersFiltered;
 		}
@@ -538,8 +531,8 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 	}
 
 	private function _createCacheTable() {
-		$db = DB::getDB();
-		$sql = "CREATE TABLE IF NOT EXISTS `_PRZYPOMNIJ_CACHE` (
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `_PRZYPOMNIJ_CACHE` (
 				`ID` int(11) NOT NULL AUTO_INCREMENT,
 				`ID_PROJECT` int(11) NOT NULL,
 				`_l_app_user` varchar(40) NOT NULL DEFAULT '',
@@ -561,24 +554,22 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 				KEY `P_ID` (`P_ID`),
 				KEY `ID_PROJECT` (`ID_PROJECT`)
 			) ENGINE=MyISAM  DEFAULT CHARSET=latin2;
-		";
-		if(V::get('DBG_P', '', $_GET) > 3){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">sql (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sql);echo'</pre>';}
-		$db->query($sql);
+		");
 	}
 
 	private function _updateCacheTable() {
-		$db = DB::getDB();
-		$sql = "truncate table `_PRZYPOMNIJ_CACHE`; ";
-		$db->query($sql);
-		$sql = "insert into `_PRZYPOMNIJ_CACHE` (`ID_PROJECT`,`P_ID`,`_l_app_user`,`L_APPOITMENT_USER`,`L_APPOITMENT_DATE`)
+		$this->_createCacheTable();
+		DB::getPDO()->execSql(" truncate table `_PRZYPOMNIJ_CACHE` ");
+		DB::getPDO()->execSql("
+			insert into `_PRZYPOMNIJ_CACHE` (`ID_PROJECT`,`P_ID`,`_l_app_user`,`L_APPOITMENT_USER`,`L_APPOITMENT_DATE`)
 			select `ID`,`P_ID`,`L_APPOITMENT_USER`,`L_APPOITMENT_USER`,`L_APPOITMENT_DATE`
 			from `IN7_MK_BAZA_DYSTRYBUCJI`
 				where 1=1
-		";
-		$db->query($sql);
+		");
 
 		// test recurse update l_app
-		$sql = "select c._l_app_user
+		$sql = "
+			select c._l_app_user
 				, p.L_APPOITMENT_USER
 				, p1.L_APPOITMENT_USER
 				, p2.L_APPOITMENT_USER
@@ -602,7 +593,8 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 		";
 
 		// for i to recurse limit
-		$sql = "update `_PRZYPOMNIJ_CACHE` as c
+		$sql = "
+			update `_PRZYPOMNIJ_CACHE` as c
 				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p on (p.ID=c.ID_PROJECT)
 				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p1 on (p1.ID=p.P_ID)
 				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p2 on (p2.ID=p1.P_ID)
@@ -625,7 +617,7 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 				)
 			where c.`_l_app_user`=''
 		";
-		$db->query($sql);
+		DB::getPDO()->execSql($sql);
 	}
 
 	public function orderByDateAsc($t1, $t2) {
@@ -917,7 +909,6 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 
 	public function sendAjaxEditAppDateInlineSave() {
 		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
-		sleep(1);// TODO: RMME DBG loading
 
 		$rowID = V::get('rowid', 0, $_POST, 'int');
 		$type = V::get('type', '', $_POST);
@@ -956,7 +947,6 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 				if ($DBG) echo "404: No field by name ({$fieldName})";
 				continue;
 			}
-			if ($DBG) echo "fieldID: {$fieldID}\n";
 
 			if (!$tblAcl->isAllowed($fieldID, 'W', $row)) {
 				if ($DBG) echo " W not allowed\n";
@@ -972,26 +962,19 @@ if(V::get('DBG_P', '', $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 				$sqlObj->{$fieldName} = $tblAcl->fixEmptyValueFromUser($fieldID);
 			}
 		}
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: save type ('.$type.') ID(' . $rowid . ') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}
 		$sqlObj->ID = $row->ID;
 
-		$dbID = $tblAcl->getDB();
-		$db = DB::getDB($dbID);
-		if (!$db) {
-			header('HTTP/1.0 406 Not Acceptable');
-			exit;
-		}
-
 		$tblName = $tblAcl->getName();
 
 		$sqlObj->ID = $rowID;
-		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: Save ('.$tblName.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}
 
 		$allData = array();
 		$allData['L_APPOITMENT_DATE'] = $sqlObj->L_APPOITMENT_DATE;
 		$allData['L_APPOITMENT_USER'] = $sqlObj->L_APPOITMENT_USER;
 		$allData['L_APPOITMENT_INFO'] = $sqlObj->L_APPOITMENT_INFO;
 
+		$dbID = $tblAcl->getDB();
+		$db = DB::getDB($dbID);
 		$ret = $db->UPDATE_OBJ($tblName, $sqlObj);
 		if ($ret > 0) {
 			echo '<div class="alert alert-success">';

+ 21 - 0
SE/se-lib/Response.php

@@ -91,6 +91,13 @@ class Response {
 			$response['body'] = $e->getBody();
 			DBG::log($e);
 			Response::sendJsonExit($response);
+		} catch (HttpException $e) {
+			Http::sendHeaderByCode($e->getCode());
+			$response['type'] = 'error';
+			$response['msg'] = $e->getMessage();
+			$response['code'] = "#" . $e->getCode() . "L" . $e->getLine();
+			DBG::log($e);
+			Response::sendJsonExit($response);
 		} catch (Exception $e) {
 			Http::sendHeaderByCode(500);
 			$response['type'] = 'error';
@@ -103,4 +110,18 @@ class Response {
 		Response::sendJsonExit($response);
 	}
 
+	static function sendRedirect($url) {
+		if (!headers_sent()) {
+			header("HTTP/1.1 303 See Other");
+			header("Location: {$url}");
+		} else {
+			echo'<script type="text/javascript">'."window.location.href='{$url}';".'</script>';
+			echo "\n".'<noscript>';
+			echo "\n".'<meta http-equiv="refresh" content="0;url='.$url.'" />';
+			echo "\n".'</noscript>';
+			echo'<p>'.'<a href="'.$url.'">'."dalej".'</a>'.'</p>';
+		}
+		exit;
+	}
+
 }

+ 12 - 5
SE/se-lib/Route/Debug.php

@@ -470,7 +470,7 @@ class Route_Debug extends RouteBase {
 			var rows = (payload && payload.body && payload.body.rows && payload.body.rows.length > 0) ? payload.body.rows : []
 			if (rows) {
 				rows.forEach(function (row) {
-					outHtml += '<tr>'
+					outHtml += ( '@class' in row ) ? '<tr class=\"' + row['@class'] + '\">' : '<tr>'
 					outHtml += cols.map(function (col) {
 						var colStyleField = '@style['+col+']'
 						if ('lp' === col) row[colStyleField] = [ row[colStyleField], 'color:#ccc;' ].join(';')
@@ -516,15 +516,20 @@ class Route_Debug extends RouteBase {
 
 	public function viewDebugRow($dbg, $lastTime) {
 		$timeDiff = (!$lastTime)
-		? ''
-		: V::milisecondsStringDiff($dbg['date'], $lastTime); // TODO: $dbg['date'] - $lastTime;
+			? ''
+			: V::milisecondsStringDiff($dbg['date'], $lastTime)
+		;
+		$uiTimeDiffClass = '';
+		if ($timeDiff > 0.5) $uiTimeDiffClass = 'danger';
+		else if ($timeDiff > 0.1) $uiTimeDiffClass = 'warning';
+		else if ($timeDiff > 0.01) $uiTimeDiffClass = 'info';
 		$trace = htmlspecialchars($dbg['trace']);
 		$trace = str_replace("\n", "\n<br>", $trace);
 		if ('#' === substr($trace, 0, 1)) $trace = "<br>{$trace}";
 		$trace = preg_replace('/<br>#(\d+\W+)([a-zA-Z0-9-_:\.\/]*)\((\d+)\):/', '<br>#${1}${2}:${3}:', $trace);
 		$trace = preg_replace('/<br>#(\d+\W+)([a-zA-Z0-9-_:\.\/]*):/', '#${1}<a href="http://localhost:9876/?project=se&file=${2}" target="_blank">${2}</a>:', $trace);
 		$trace = str_replace("\n<br>", "\n", $trace);
-		return [
+		return array_merge([
 			'date' => '<nobr>' . substr($dbg['date'], 11) . '</nobr>',
 			'diff' => '<nobr>' . $timeDiff . '</nobr>',
 			'@style[date]' => "width:1%",
@@ -561,7 +566,9 @@ class Route_Debug extends RouteBase {
 					),
 				]),
 			]),
-		];
+		],
+			($uiTimeDiffClass) ? [ '@class' => $uiTimeDiffClass ] : []
+		);
 	}
 
 	public function fetchMoreDbgLinesAjaxAction() {

+ 20 - 2
SE/se-lib/Route/P5Menu.php

@@ -49,6 +49,12 @@ class Route_P5Menu extends RouteBase {
 		$bookmarks = UserBookmarks::getInstance()->getBookmarks();
 		$makeShortLabel = [ $this, 'getRawLabel' ];
 
+		$sqlIds = implode(", ", array_keys($listObjects));
+		$cacheZasobInfo = DB::getPDO()->fetchAllByKey("
+			select `ID`, `DESC`, `DESC_PL`, `OPIS`
+			from CRM_LISTA_ZASOBOW
+			where ID in ({$sqlIds})
+		", $key = 'ID');
 		return [
 			'type' => 'success',
 			'msg' => "OK",
@@ -56,13 +62,25 @@ class Route_P5Menu extends RouteBase {
 				'version' => $this->getVersion(),
 
 				'objects' => array_values( array_filter(
-					array_map(function ($acl, $idZasob) use ($makeShortLabel) {
+					array_map(function ($acl, $idZasob) use ($makeShortLabel, $cacheZasobInfo) {
 						if (!$acl) return [
 							'id' => $idZasob,
 							'TODO' => 'TODO'
 						];
 
-						$zasobObj = ProcesHelper::getZasobTableInfo($acl->getID());
+						// $zasobObj = ProcesHelper::getZasobTableInfo($acl->getID());
+						// $zasobObj = (object)DB::getPDO()->fetchFirst("
+						// 	select `ID`, `DESC`, `DESC_PL`, `OPIS`
+						// 	from CRM_LISTA_ZASOBOW
+						// 	where ID = :id
+						// ", [ ':id' => $acl->getID() ]);
+						// if ((int)$idZasob !== (int)$acl->getID()) throw new Exception("BUG idZasob !== Acl->getID() : ({$idZasob}) !== (".$acl->getID().")");
+						if (!array_key_exists($acl->getID(), $cacheZasobInfo)) return [
+							'id' => $idZasob,
+							'TODO' => "Zasób '{$idZasob}' nie istnieje w bazie"
+						];
+
+						$zasobObj = (object)$cacheZasobInfo[$acl->getID()];
 						return [
 							'id' => $acl->getID(),
 							'namespace' => $acl->getNamespace(),

+ 5 - 1
SE/se-lib/Route/Storage.php

@@ -434,7 +434,11 @@ class Route_Storage extends RouteBase {
 		if (!$item['idZasob']) throw new Exception("Missing id zasob for object '{$namespace}'");
 		if (!$item['idDatabase']) throw new Exception("Missing id database for object '{$namespace}'");
 		if (!$item['_rootTableName']) throw new Exception("Missing root table name for object '{$namespace}'");
-		if ('AntAcl' != $item['_type']) throw new Exception("Not implemented type '{$item['_type']}' for namespace '{$namespace}' - only AntAcl supported");
+		switch ($item['_type']) {
+			case 'AntAcl': break; // OK
+			case 'StorageAcl': break; // OK
+			default: throw new Exception("Not implemented type '{$item['_type']}' for namespace '{$namespace}' - only AntAcl supported");
+		}
 		if (!$item['hasStruct']) throw new Exception("Missing structure for object '{$namespace}'");
 		if (!$item['isStructInstalled']) throw new Exception("Structure not installed for object '{$namespace}'");
 		if (!$item['isObjectActive']) throw new Exception("Object is not active '{$namespace}'");

+ 82 - 5
SE/se-lib/Route/ViewTableAjax.php

@@ -50,11 +50,17 @@ class Route_ViewTableAjax extends RouteBase {
 		$tbl->showTableTools = $this->getLink("tableToolsAjax", [ 'namespace' => $acl->getNamespace() ]);
 		$tbl->useUserTableFilter = $this->getLink("getUserTableFilterAjax");
 		$tbl->setLabel($tblLabel);
-		$tbl->addRowFunction('edit');
-		$tbl->addRowFunction('hist');
-		$tbl->addRowFunction('files');
-		$tbl->addRowFunction('cp');
-		$tbl->addRowFunction('msgs');
+		if (method_exists($acl, 'getGuiRowFunctions')) {
+			foreach ($acl->getGuiRowFunctions() as $funKey => $funParams) {
+				$tbl->addRowFunction($funKey, $funParams);
+			}
+		} else {
+			$tbl->addRowFunction('edit');
+			$tbl->addRowFunction('hist');
+			$tbl->addRowFunction('files');
+			$tbl->addRowFunction('cp');
+			$tbl->addRowFunction('msgs');
+		}
 		return $tbl;
 	}
 
@@ -1108,6 +1114,64 @@ class Route_ViewTableAjax extends RouteBase {
 		return $jsonData;
 	}
 
+	public function typespecialAction() { Response::sendTryCatchJson(array($this, 'typespecial'), $args = $_REQUEST); }
+	public function typespecial($args) { // @required idField, @optional: q (query), selected (selected value), idRecord (fetch value for given record)
+		$idField = V::get('idField', 0, $args, 'int');
+		if (!$idField) throw new HttpException("Bad Request - missing idField", 400);
+		$query = V::get('q', '', $_REQUEST);
+		DBG::log("\$query({$query})");
+
+		$cellInfo = DB::getPDO()->fetchFirst("
+			select *
+			from CRM_PROCES_idx_TABLES_INFO_VIEW
+			where ID_CELL = :id
+		", [ ':id' => $idField ]);
+		DBG::log($cellInfo, 'array', "cell info");
+		if (!$cellInfo) throw new HttpException("Bad Request - wrong idField", 400);
+		// 'ID_CELL' => '24310',
+		// 'CELL_NAME' => 'L_APPOITMENT_USER',
+		// 'CELL_LABEL' => 'Osoba odpowiedzialna',
+		// 'CELL_DESCRIPTION' => '',
+		// 'CELL_SORT_PRIO' => '3',
+		// 'ID_TABLE' => '13051',
+		// 'TABLE_NAME' => 'TEST_PERMS',
+		// 'TABLE_LABEL' => 'Test permy',
+		// 'TABLE_DESCRIPTION' => '',
+		// 'ID_DATABASE' => '36',
+		$namespace = (false !== strpos($cellInfo['TABLE_NAME'], '/')) ? $cellInfo['TABLE_NAME'] : "default_db/{$cellInfo['TABLE_NAME']}";
+		if (!$namespace) throw new HttpException("Bad Request - wrong idField, cannot find namespace", 400);
+
+		$acl = Core_AclHelper::getAclByNamespace($namespace);
+		$fieldName = $cellInfo['CELL_NAME'];
+
+		$jsonData = array();
+		$typeSpecial = Typespecial::getInstance($idField, $fieldName);
+		if ($typeSpecial) {
+
+			// if (idRecord or selected) { // TODO
+			// $jsonData->data = $typeSpecial->getReturnData($acl->getID(), $id, $fieldName, '');
+			// $jsonData->namespace = 'default_db/' . V::get('tbl_name', '', $jsonData->data);
+
+			$rawRows = null;
+			$rows = $typeSpecial->getValuesWithExports($query);
+			DBG::log($rows, 'array', "\$rows({$query})");
+			foreach ($rows as $kID => $vItem) {
+				$itemJson = new stdClass();
+				$itemJson->id = $vItem->id;
+				$itemJson->name = $vItem->param_out;
+				if (!empty($vItem->exports)) {
+					$itemJson->exports = $vItem->exports;
+				}
+				if (!empty($vItem->{'$order'})) {
+					$itemJson->{'$order'} = $vItem->{'$order'};
+				}
+				$jsonData[] = $itemJson;
+			}
+		}
+
+		return $jsonData;
+	}
+
 	/**
 	 * @param $_GET['namespace'] = AclNamespace
 	 * @param $_GET['format'] = 'csv' | 'html'
@@ -1467,4 +1531,17 @@ class Route_ViewTableAjax extends RouteBase {
 		];
 	}
 
+	function executeRowFunctionAction() { UI::layout([ $this, 'executeRowFunction' ]); }
+	function executeRowFunction() {
+		$namespace = V::get('namespace', '', $_GET);
+		$name = V::get('name', '', $_GET);
+		$pk = V::get('pk', '', $_GET);
+		if (!$namespace) throw new Exception("Missing namespace");
+		if (!$name) throw new Exception("Missing name");
+		if (!$pk) throw new Exception("Missing pk");
+		$acl = ACL::getAclByNamespace($namespace);
+		if (!method_exists($acl, 'executeGuiRowFunction')) throw new Exception("Function executeGuiRowFunction not defined for '{$namespace}'");
+		$acl->executeGuiRowFunction($name, $pk);
+	}
+
 }

+ 896 - 0
SE/se-lib/Schema/PrzypomnijStorageAcl.php

@@ -0,0 +1,896 @@
+<?php
+
+Lib::loadClass('Core_AclSimpleSchemaBase');
+Lib::loadClass('ParseOgcFilter');
+Lib::loadClass('Router');
+Lib::loadClass('SchemaVersionUpgrade');
+
+class Schema_PrzypomnijStorageAcl extends Core_AclSimpleSchemaBase {
+
+	public $_simpleSchema = [
+		'root' => [
+			'@namespace' => 'default_objects/Przypomnij',
+			'@primaryKey' => 'feature_id',
+			// col: Typ rekordu / ID
+			'feature_id' => [ '@type' => 'xsd:string' ], // eg.: "PROBLEMS.123"
+			'namespace' => [ '@type' => 'xsd:string' ], // eg.: "default_db/PROBLEMS"
+			'primaryKey' => [ '@type' => 'xsd:integer' ], // eg.: 123
+			'A_STATUS' => [ '@type' => 'xsd:string' ],
+			'featureType' => [ '@type' => 'xsd:string' ], // eg.: Zasob.TYPE, Projekt.M_DIST_TYPE, etc.
+			'featureDesc' => [ '@type' => 'xsd:string' ], // eg.: Zasob.DESC, Projekt.M_DIST_DESC, etc.
+
+			'L_APPOITMENT_USER' => [ '@type' => 'xsd:string' ], // Osoba odpowiedzialna
+			'A_ADM_COMPANY' =>  [ '@type' => 'xsd:string' ],
+			'A_CLASSIFIED' =>  [ '@type' => 'xsd:string' ],
+
+			// col: Termin wykonania / Opis działań do wykonania
+			'L_APPOITMENT_DATE' => [ '@type' => 'xsd:dateTime' ], // Termin wykonania
+			'L_APPOITMENT_INFO' => [ '@type' => 'xsd:string' ], // Opis działań do wykonania
+			// Firma powiąz. / adres / opis-temat
+			// Lokalizacja
+
+			'PROJECT__ID' => [ '@type' => "xsd:int" ],
+			'PROJECT__L_APPOITMENT_USER' => [ '@type' => "xsd:string" ],
+			'USER__IS_ACTIVE' => [ '@type' => "xsd:int" ],
+
+			// 'idZasob' => [ '@type' => 'xsd:integer' ],
+			// 'idDatabase' => [ '@type' => 'xsd:integer' ],
+			// '_rootTableName' => [ '@type' => 'xsd:string' ],
+			// '_type' => [ '@type' => 'xsd:string' ],
+			// 'hasStruct' => [ '@type' => 'xsd:integer' ], // 0 - removed, old, 1 - has config, structure
+			// 'isStructInstalled' => [ '@type' => 'xsd:integer' ], // installed
+			// 'isObjectActive' => [ '@type' => 'xsd:integer' ], // (0,1) - admin settings with restrictions: (hasStruct, isStructInstalled, all fields installed and with idZasob)
+			// 'description' => [ '@type' => 'xsd:string' ],
+			// 'name' => [ '@type' => 'p5:string' ],
+			// 'typeName' => [ '@type' => 'p5:string' ],
+			// 'nsPrefix' => [ '@type' => 'p5:string' ],
+			// 'reinstallLink' => [ '@type' => 'p5:www_link' ],
+			// 'instanceTableSource' => [ '@type' => 'xsd:string' ], // enum('table', 'view') default 'view'
+			// 'A_RECORD_CREATE_AUTHOR' => [ '@type' => 'xsd:string' , '@label' => 'autor' ],
+			// 'A_RECORD_CREATE_DATE' => [ '@type' => 'xsd:date' , '@label' => 'utworzono' ],
+			// 'A_RECORD_UPDATE_AUTHOR' => [ '@type' => 'xsd:string' , '@label' => 'zaktualizował' ],
+			// 'A_RECORD_UPDATE_DATE' => [ '@type' => 'xsd:date', '@label' => 'zaktualizowano' ],
+		]
+	];
+	// public $_rootTableName = 'CRM_LISTA_ZASOBOW';
+	public $_rootTableName = '_PRZYPOMNIJ_ITEMS';
+	static function _createItemsTable() {
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `_PRZYPOMNIJ_ITEMS` (
+				`L_APPOITMENT_USER` varchar(255) NOT NULL default '',
+				`A_ADM_COMPANY` varchar(255) NOT NULL default '',
+				`A_CLASSIFIED` varchar(255) NOT NULL default '',
+				`feature_id` varchar(255) NOT NULL,
+				`namespace` varchar(255) NOT NULL default '',
+				`primaryKey` varchar(255) NOT NULL default '',
+				`A_STATUS` varchar(16) NOT NULL default '',
+				`featureType` varchar(32) NOT NULL default '',
+				`featureDesc` varchar(255) NOT NULL default '',
+				`L_APPOITMENT_DATE` dateTime DEFAULT NULL,
+				`L_APPOITMENT_INFO` varchar(255) NOT NULL default '',
+				`PROJECT__ID` int(11) NOT NULL DEFAULT 0,
+				`PROJECT__L_APPOITMENT_USER` varchar(255) NOT NULL default '',
+				`USER__IS_ACTIVE` tinyint(1) NOT NULL DEFAULT 0,
+
+				PRIMARY KEY (`feature_id`),
+				KEY `namespace` (`namespace`),
+				KEY `L_APPOITMENT_USER` (`L_APPOITMENT_USER`),
+				KEY `A_ADM_COMPANY` (`A_ADM_COMPANY`),
+				KEY `A_CLASSIFIED` (`A_CLASSIFIED`)
+
+				-- `ID_PROJECT` int(11) NOT NULL,
+				-- `_l_app_user` varchar(40) NOT NULL DEFAULT '',
+				-- `P_ID` varchar(20) NOT NULL DEFAULT '0',
+				-- `A_RECORD_CREATE_DATE` datetime NOT NULL,
+				-- `A_RECORD_CREATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
+				-- `A_RECORD_UPDATE_DATE` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+				-- `A_RECORD_UPDATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
+				-- `L_APPOITMENT_DATE` varchar(30) NOT NULL DEFAULT '',
+				-- `L_APPOITMENT_USER` varchar(40) NOT NULL DEFAULT '',
+				-- `L_APPOITMENT_INFO` varchar(200) NOT NULL DEFAULT '',
+				-- `L_CALENDAR` enum('NO','YES') NOT NULL DEFAULT 'NO',
+				-- `A_STATUS` enum('WAITING','NORMAL','MONITOR','WARNING','OFF_SOFT','OFF_HARD','DELETED') NOT NULL DEFAULT 'WAITING',
+				-- `M_DIST_DATE` date NOT NULL DEFAULT '0000-00-00',
+				-- `M_DIST_TYPE` varchar(64) NOT NULL DEFAULT 'INNE',
+				-- `M_DIST_DESC` varchar(255) NOT NULL DEFAULT '',
+				-- `M_DISTRIBUTOR` varchar(200) NOT NULL DEFAULT '',
+				-- KEY `ID_PROJECT` (`ID_PROJECT`)
+			) ENGINE=MyISAM  DEFAULT CHARSET=latin2;
+		");
+	}
+
+	function __construct() {
+		parent::__construct($this->_simpleSchema);
+		$this->idUser = User::getID(); // default - current user
+	}
+
+	function onBeforeFetchData() {
+		self::updateCacheIfNeeded();
+	}
+
+	static function updateCacheIfNeeded() {
+		static $_checked = false;
+		if ($_checked) return;
+
+		$confKeyLastUpdateDate = 'Schema_PrzypomnijStorageAcl::lastUpdateDate';
+		$lastUpdateDate = DB::getPDO()->fetchValue(" select CONF_VAL from CRM_CONFIG where CONF_KEY = :conf_key ", [ ':conf_key'  => $confKeyLastUpdateDate ]);
+		$tablesList = [];
+		$tablesList[] = '_PRZYPOMNIJ_ITEMS'; // to check if tables exists
+		$tablesList[] = '_PRZYPOMNIJ_PROJECT_TREE'; // to check if tables exists
+		$tablesList[] = '_PRZYPOMNIJ_ACTIVE_USERS'; // to check if tables exists
+		$tablesList[] = 'IN7_MK_BAZA_DYSTRYBUCJI';
+		$tablesList[] = 'IN7_DZIENNIK_KORESP';
+		$tablesList[] = 'CRM_PROCES';
+		$tablesList[] = 'PROBLEMS';
+		$tablesList[] = 'CRM_LISTA_ZASOBOW';
+		$tablesList[] = 'MK_Rewiry';
+		$tablesList[] = 'BUILDINGS';
+		$tablesList[] = 'QUALITY_NOTICES';
+		$tablesList[] = 'BADANIA_W_TERENIE';
+		$sqlTableList = implode(", ", array_map(function ($table) { return "'{$table}'"; }, $tablesList));
+		$tablesUpdateDates = DB::getPDO()->fetchValuesListByKey("
+			select IF( UPDATE_TIME > :last_update_date, 1, 0 ) as TO_UPDATE
+				, TABLE_NAME
+				, UPDATE_TIME
+			from information_schema.tables
+			where TABLE_SCHEMA = :db_name
+				and TABLE_NAME in({$sqlTableList})
+		", $key = 'TABLE_NAME', [
+			':db_name' => DB::getPDO()->getDatabaseName(),
+			':last_update_date' => $lastUpdateDate,
+		]);
+		DBG::log($tablesUpdateDates, 'array', "DBG::Przypomnij: \$tablesUpdateDates 1");
+		if (!array_key_exists('_PRZYPOMNIJ_ITEMS', $tablesUpdateDates)) {
+			self::_createItemsTable();
+			$tablesUpdateDates = array_map(function ($value) { return 1; }, $tablesUpdateDates);
+		}
+		if (!array_key_exists('_PRZYPOMNIJ_PROJECT_TREE', $tablesUpdateDates)
+			|| $tablesUpdateDates['IN7_MK_BAZA_DYSTRYBUCJI']
+		) {
+			if (!array_key_exists('_PRZYPOMNIJ_PROJECT_TREE', $tablesUpdateDates)) {
+				self::_createProjectsTreeTable();
+			}
+			if (self::_isProjectsOwnerChanged()) {
+				self::_updateProjectsTreeTable();
+			}
+			// $tablesUpdateDates = array_map(function ($value) { return 1; }, $tablesUpdateDates); // moved to mass update
+		}
+		if (!array_key_exists('_PRZYPOMNIJ_ACTIVE_USERS', $tablesUpdateDates)
+			|| $tablesUpdateDates['ADMIN_USERS']
+		) {
+			if (!array_key_exists('_PRZYPOMNIJ_ACTIVE_USERS', $tablesUpdateDates)) {
+				self::_createUsersTable();
+			}
+			if (self::_isUsersChanged()) {
+				self::_updateUsersStatusTable();
+			}
+			// $tablesUpdateDates = array_map(function ($value) { return 1; }, $tablesUpdateDates); // moved to mass update
+		}
+		DBG::log($tablesUpdateDates, 'array', "DBG::Przypomnij: \$tablesUpdateDates 2");
+
+		$tablesToUpdate = array_keys(
+			array_filter($tablesUpdateDates, function ($value) { return $value; })
+		);
+		$tablesToUpdate = array_filter($tablesToUpdate, function ($tableName) { return ( '_PRZYPOMNIJ_' !== substr($tableName, 0, strlen('_PRZYPOMNIJ_')) ); });
+		DBG::log($tablesToUpdate, 'array', "DBG::Przypomnij: \$tablesToUpdate");
+		foreach ($tablesToUpdate as $tableName) self::_updateForTable($tableName);
+
+		if (!array_key_exists('_PRZYPOMNIJ_PROJECT_TREE', $tablesUpdateDates)
+			|| $tablesUpdateDates['IN7_MK_BAZA_DYSTRYBUCJI']
+		) {
+			self::_updateLAppUserByProjectsTree();
+		}
+		if (!array_key_exists('_PRZYPOMNIJ_ACTIVE_USERS', $tablesUpdateDates)
+			|| $tablesUpdateDates['ADMIN_USERS']
+			|| !empty($tablesToUpdate) // update if anything updates
+		) {
+			self::_updateUserStatus();
+		}
+
+		DB::getPDO()->insertOrUpdate('CRM_CONFIG', [
+			'CONF_KEY' => $confKeyLastUpdateDate,
+			'@insert' => [
+				'CONF_VAL' => "NOW()",
+			],
+			'@update' => [
+				'CONF_VAL' => "NOW()",
+			]
+		]);
+		$_checked = true;
+	}
+
+	static function _updateForTable($tableName) {
+		$namespace = "default_db/{$tableName}";
+		if ('CRM_LISTA_ZASOBOW' === $tableName) { // TODO: getUpdateConfigForTable($tableName);
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, t.`TYPE` as `featureType`
+					, t.`DESC` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+				from `CRM_LISTA_ZASOBOW` as t
+				where t.`A_STATUS` in ('NORMAL', 'WAITING')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('CRM_PROCES' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, t.`TYPE` as `featureType`
+					, t.`DESC` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+				from `CRM_PROCES` as t
+				where t.`A_STATUS` in ('NORMAL', 'WAITING')
+					and t.`TYPE` = 'PROCES_INIT'
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('IN7_DZIENNIK_KORESP' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, concat(t.`K_TYP_KORESP`, '-', t.`K_TYP_RODZAJ`) as `featureType`
+					, concat('<strong>', t.`K_OD_KOGO`, '</strong><br><em>', t.`OD_KOGO_ADRES`, '</em><br>', t.`K_ZAWARTOS`) as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, t.`ID_PROJECT` as PROJECT__ID
+				from `IN7_DZIENNIK_KORESP` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('IN7_MK_BAZA_DYSTRYBUCJI' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'M_DIST_DESC' => htmlspecialchars($row['M_DIST_DESC']), // TODO: fix bug in html a href inside M_DIST_DES
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, t.`M_DIST_TYPE` as `featureType`
+					, t.`M_DIST_DESC` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, t.`P_ID` as PROJECT__ID
+				from `IN7_MK_BAZA_DYSTRYBUCJI` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('PROBLEMS' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, '' as `featureType`
+					, t.`A_PROBLEM_DESC` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, t.`ID_PROJECT` as PROJECT__ID
+				from `PROBLEMS` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('MK_Rewiry' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, '' as `featureType`
+					, t.`L_APPOITMENT_INFO` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, t.`ID_PROJECT` as PROJECT__ID
+				from `MK_Rewiry` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('BUILDINGS' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, '' as `featureType`
+					, t.`L_APPOITMENT_INFO` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, t.`ID_PROJECT` as PROJECT__ID
+				from `BUILDINGS` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('QUALITY_NOTICES' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, t.`NOTICE_INITIAL_TYPE` as `featureType`
+					, t.`NOTICE_REQUEST` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, 0 as PROJECT__ID
+				from `QUALITY_NOTICES` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('BADANIA_W_TERENIE' === $tableName)  { // TODO: getUpdateConfigForTable($tableName);
+			// TODO: 'A_PROBLEM_DESC' => htmlspecialchars($row['A_PROBLEM_DESC']),
+			DB::getPDO()->execSql(" DELETE from `_PRZYPOMNIJ_ITEMS` where `namespace` = :namespace ", [ ':namespace' => $namespace ]);
+			DB::getPDO()->execSql("
+				insert into `_PRZYPOMNIJ_ITEMS` (
+					`feature_id`, `namespace`, `primaryKey`
+					, `A_STATUS`, `featureType`, `featureDesc`
+					, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`
+					, `L_APPOITMENT_DATE`, `L_APPOITMENT_INFO`
+					, `PROJECT__ID`
+				)
+				select concat( :table_name , '.', t.ID) as `feature_id`, :namespace as `namespace`, t.ID as `primaryKey`
+					, t.`A_STATUS`
+					, '' as `featureType`
+					, t.`L_APPOITMENT_INFO` as `featureDesc`
+					, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
+					, t.`L_APPOITMENT_DATE`, t.`L_APPOITMENT_INFO`
+					, 0 as PROJECT__ID
+				from `BADANIA_W_TERENIE` as t
+				where t.`A_STATUS` not in ('OFF_HARD', 'DELETED')
+					and t.`L_APPOITMENT_DATE` != ''
+					and t.`L_APPOITMENT_USER` != ''
+			", [
+				':namespace' => $namespace,
+				':table_name' => $tableName,
+			]);
+		}
+		else if ('_PRZYPOMNIJ_ITEMS' === $tableName) {}
+		else if ('_PRZYPOMNIJ_PROJECT_TREE' === $tableName) {}
+		else {
+			throw new Exception("TODO: (default_objects/Przypomnij)::_updateForTable({$tableName})");
+		}
+	}
+
+	static function _createUsersTable() {
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `_PRZYPOMNIJ_ACTIVE_USERS` (
+				`ADM_ACCOUNT` varchar(20) NOT NULL,
+				UNIQUE KEY `ADM_ACCOUNT` (`ADM_ACCOUNT`)
+			) ENGINE=MyISAM  DEFAULT CHARSET=latin2;
+		");
+	}
+	static function _isUsersChanged() {
+		return ( DB::getPDO()->fetchValue("
+			select count(1) as total
+				-- u.ID as u__ID, u.ADM_ACCOUNT as u__ADM_ACCOUNT, t.*
+			from ADMIN_USERS u
+				left join _PRZYPOMNIJ_ACTIVE_USERS t on ( t.ADM_ACCOUNT = u.ADM_ACCOUNT )
+			where t.ADM_ACCOUNT is null
+		") > 0);
+	}
+	static function _updateUsersStatusTable() {
+		DB::getPDO()->execSql(" truncate table `_PRZYPOMNIJ_ACTIVE_USERS` ");
+		DB::getPDO()->execSql("
+			insert into `_PRZYPOMNIJ_ACTIVE_USERS` (`ADM_ACCOUNT`)
+			select `ADM_ACCOUNT`
+			from `ADMIN_USERS`
+			where A_STATUS = 'NORMAL'
+		");
+	}
+	static function _updateUserStatus() {
+		DB::getPDO()->execSql("
+			update `_PRZYPOMNIJ_ITEMS` as i
+				left join `_PRZYPOMNIJ_ACTIVE_USERS` as u on ( u.ADM_ACCOUNT = i.L_APPOITMENT_USER )
+			set i.USER__IS_ACTIVE = IF(u.ADM_ACCOUNT is null, 0, 1)
+			where i.L_APPOITMENT_USER != ''
+		");
+	}
+
+	static function _createProjectsTreeTable() {
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `_PRZYPOMNIJ_PROJECT_TREE` (
+				`ID_PROJECT` int(11) NOT NULL,
+				`P_ID` int(11) NOT NULL DEFAULT 0,
+				`L_APPOITMENT_USER` varchar(40) NOT NULL DEFAULT '',
+				`_l_app_user` varchar(40) NOT NULL DEFAULT '',
+				UNIQUE KEY `ID_PROJECT` (`ID_PROJECT`),
+				KEY `P_ID` (`P_ID`)
+			) ENGINE=MyISAM  DEFAULT CHARSET=latin2;
+		");
+	}
+	static function _isProjectsOwnerChanged() {
+		return ( DB::getPDO()->fetchValue("
+			select count(1) as total
+				-- p.ID as p__ID, p.L_APPOITMENT_USER as p__L_APPOITMENT_USER, t.*
+			from IN7_MK_BAZA_DYSTRYBUCJI p
+				left join _PRZYPOMNIJ_PROJECT_TREE t on ( t.ID_PROJECT = p.ID )
+			where (
+				t.L_APPOITMENT_USER is null
+				or p.L_APPOITMENT_USER != t.L_APPOITMENT_USER
+			)
+		") > 0);
+	}
+	static function _updateProjectsTreeTable() {
+		DB::getPDO()->execSql(" truncate table `_PRZYPOMNIJ_PROJECT_TREE` ");
+		DB::getPDO()->execSql("
+			insert into `_PRZYPOMNIJ_PROJECT_TREE` (`ID_PROJECT`,`P_ID`,`_l_app_user`,`L_APPOITMENT_USER`)
+			select `ID`,`P_ID`,`L_APPOITMENT_USER`,`L_APPOITMENT_USER`
+			from `IN7_MK_BAZA_DYSTRYBUCJI`
+				where 1=1
+		");
+
+		// test recurse update l_app
+		$sql = "
+			select c._l_app_user
+				, p.L_APPOITMENT_USER
+				, p1.L_APPOITMENT_USER
+				, p2.L_APPOITMENT_USER
+				, p3.L_APPOITMENT_USER
+				, p4.L_APPOITMENT_USER
+				, p5.L_APPOITMENT_USER
+				, p.ID
+				, p1.ID
+				, p2.ID
+				, p3.ID
+				, p4.ID
+				, p5.ID
+			from `_PRZYPOMNIJ_PROJECT_TREE` as c
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p on (p.ID=c.ID_PROJECT)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p1 on (p1.ID=p.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p2 on (p2.ID=p1.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p3 on (p3.ID=p2.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p4 on (p4.ID=p3.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p5 on (p5.ID=p4.P_ID)
+			where c.`_l_app_user`=''
+		";
+
+		// for i to recurse limit
+		$sql = "
+			update `_PRZYPOMNIJ_PROJECT_TREE` as c
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p on (p.ID=c.ID_PROJECT)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p1 on (p1.ID=p.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p2 on (p2.ID=p1.P_ID)
+				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p3 on (p3.ID=p2.P_ID)
+			--	left join `IN7_MK_BAZA_DYSTRYBUCJI` as p4 on (p4.ID=p3.P_ID)
+			--	left join `IN7_MK_BAZA_DYSTRYBUCJI` as p5 on (p5.ID=p4.P_ID)
+			set
+				c.`_l_app_user`=IF(p.`L_APPOITMENT_USER`!='', p.`L_APPOITMENT_USER`
+					, IF(p1.`L_APPOITMENT_USER`!='', p1.`L_APPOITMENT_USER`
+						, IF(p2.`L_APPOITMENT_USER`!='', p2.`L_APPOITMENT_USER`
+							, IF(p3.`L_APPOITMENT_USER`!='', p3.`L_APPOITMENT_USER`
+			--					, IF(p4.`L_APPOITMENT_USER`!='', p4.`L_APPOITMENT_USER`
+			--						, IF(p5.`L_APPOITMENT_USER`!='', p5.`L_APPOITMENT_USER`
+										, ''
+			--						)
+			--					)
+							)
+						)
+					)
+				)
+			where c.`_l_app_user`=''
+		";
+		DB::getPDO()->execSql($sql);
+	}
+	static function _updateLAppUserByProjectsTree() {
+		DB::getPDO()->execSql("
+			update `_PRZYPOMNIJ_ITEMS` as i
+				join `_PRZYPOMNIJ_PROJECT_TREE` as t on ( t.ID_PROJECT = i.PROJECT__ID )
+			set i.PROJECT__L_APPOITMENT_USER = t._l_app_user
+			where i.PROJECT__ID > 0
+		");
+	}
+
+	// function getTotal($params = []) {
+	// 	self::updateCacheIfNeeded();
+	// 	$sqlWhere = $this->_parseWhere($params);
+	// 	return DB::getPDO()->fetchValue("
+	// 		select count(1) as cnt
+	// 		from `{$this->_rootTableName}` t
+	// 		{$sqlWhere}
+	// 	");
+	// }
+	// function getItem($pk, $params = []) {
+	// 	self::updateCacheIfNeeded();
+	// 	if (!$pk) throw new Exception("Missing primary key '{$this->_namespace}'");
+	// 	$pkField = $this->getSqlPrimaryKeyField();
+	// 	if (!$pkField) throw new Exception("Missing primary key field defined in '{$this->_namespace}'");
+	// 	$item = DB::getPDO()->fetchFirst("
+	// 		select t.*
+	// 		from `{$this->_rootTableName}` t
+	// 		where t.`{$pkField}` = :pk
+	// 	", [ ':pk' => $pk ]);
+	// 	if (!$item) throw new Exception("Item '{$pk}' not exists - type '{$this->_namespace}'");
+	// 	return $this->buildFeatureFromSqlRow($item, $params);
+	// }
+	//
+	// function getItems($params = []) {
+	// 	self::updateCacheIfNeeded();
+	// 	$sqlWhere = $this->_parseWhere($params);
+	//
+	// 	$currSortCol = V::get('order_by', 'feature_id', $params);
+	// 	$currSortFlip = strtolower(V::get('order_dir', 'desc', $params));
+	// 	// TODO: validate $currSortCol is in field list
+	// 	// TODO: validate $currSortFlip ('asc' or 'desc')
+	// 	$xsdFields = $this->getXsdTypes();
+	// 	if (!array_key_exists($currSortCol, $xsdFields)) throw new Exception("Field '{$currSortCol}' not found in '{$this->_namespace}'");
+	// 	if (!in_array($currSortFlip, ['asc', 'desc'])) throw new Exception("Sort dir not allowed");
+	// 	$sqlOrderBy = "order by t.`{$currSortCol}` {$currSortFlip}";
+	//
+	// 	$limit = V::get('limit', 0, $params, 'int');
+	// 	$limit = ($limit < 0) ? 0 : $limit;
+	// 	$offset = V::get('limitstart', 0, $params, 'int');
+	// 	$offset = ($offset < 0) ? 0 : $offset;
+	// 	$sqlLimit = ($limit > 0)
+	// 	? "limit {$limit} offset {$offset}"
+	// 	: '';
+	//
+	// 	Lib::loadClass('AclQueryItems');
+	// 	$query = new AclQueryItems($this);
+	// 	$query->setParams($params);
+	// 	$query->setSource('default_db');
+	// 	$query->setRawSql("
+	// 		select t.*
+	// 		from `{$this->_rootTableName}` t
+	// 		{$sqlWhere}
+	// 		{$sqlOrderBy}
+	// 		{$sqlLimit}
+	// 	");
+	// 	return array_map(function ($item) use ($params) {
+	// 		return $this->buildFeatureFromSqlRow($item, $params);
+	// 	}, $query->fetchAll());
+	// }
+
+	function buildQuery($params = array()) {
+		Lib::loadClass('AclQueryFeatures');
+		return new AclQueryFeatures($this, $params, $legacyMode = false);
+	}
+
+	function _parseWhere($params = []) {
+		$sqlWhere = [];
+		DBG::log($params, 'array', "SystemObject::_parseWhere");
+		// if (!empty($params['#refFrom'])) {
+		// 	// '#refFrom' => [
+		// 	//	 'namespace' => 'default_objects/SystemSource',
+		// 	//	 'primaryKey' => $sourceItem['idZasob']
+		// 	// ]
+		// 	if (empty($params['#refFrom']['namespace'])) throw new Exception("Missing refFrom/namespace");
+		// 	if (empty($params['#refFrom']['primaryKey'])) throw new Exception("Missing refFrom/primaryKey");
+		//
+		// 	if ('default_objects/SystemSource' != $params['#refFrom']['namespace']) throw new Exception("Unsupported refFrom/namespace '{$params['#refFrom']['namespace']}'");
+		// 	$sqlWhere[] = "t.idDatabase = " . DB::getPDO()->quote($params['#refFrom']['primaryKey'], PDO::PARAM_INT);
+		// }
+		{
+			$filterParams = [];
+			$xsdFields = $this->getXsdTypes();
+			foreach ($params as $k => $v) {
+				if ('f_' != substr($k, 0, 2)) continue;
+				$fieldName = substr($k, 2);
+				if (!array_key_exists($fieldName, $xsdFields)) {
+					// TODO: check query by xpath or use different param prefix
+					throw new Exception("Field '{$fieldName}' not found in '{$this->_namespace}'");
+				}
+				if ('p5:www_link' == $xsdFields[$fieldName]) {
+					continue;
+				}
+				$filterParams[$fieldName] = $v;
+			}
+		}
+		if (!empty($filterParams)) {
+			DBG::log($filterParams, 'array', "SystemObject::_parseWhere TODO \$filterParams");
+			foreach ($filterParams as $fieldName => $value) {
+				if (is_array($value)) {
+					DBG::log($value, 'array', "TODO SystemObject::_parseWhere array value for \$filterParams[{$fieldName}]");
+				} else if (is_scalar($value)) {
+					if ('=' == substr($value, 0, 1)) {
+						$sqlWhere[] = "t.{$fieldName} = " . DB::getPDO()->quote(substr($value, 1), PDO::PARAM_STR);
+					} else {
+						$sqlWhere[] = "t.{$fieldName} like " . DB::getPDO()->quote("%{$value}%", PDO::PARAM_STR);
+					}
+				} else {
+					DBG::log($value, 'array', "BUG SystemObject::_parseWhere unknown type for \$filterParams[{$fieldName}]");
+				}
+			}
+		}
+		{ // acl fields
+			$sqlWhere[] = "t.{$fieldName} like " . DB::getPDO()->quote("%{$value}%", PDO::PARAM_STR);
+		}
+		return (!empty($sqlWhere)) ? "where " . implode(" and ", $sqlWhere) : '';
+	}
+
+	function buildFeatureFromSqlRow($item, $params = []) {
+		// DBG::log($params, 'array', "buildFeatureFromSqlRow... '{$item['namespace']}'");
+		// $exNs = explode('/', $item['namespace']);
+		// $item['name'] = array_pop($exNs);
+		// $item['nsPrefix'] = implode('__x3A__', $exNs);
+		// $item['typeName'] = implode('__x3A__', $exNs) . ':' . $item['name'];
+		// $item['reinstallLink'] = Router::getRoute('Storage_AclReinstall')->getLink('', [ 'namespace' => $item['namespace'] ]);
+		// if (!empty($params['propertyName'])) {
+		// 	if (is_string($params['propertyName'])) $params['propertyName'] = explode(',', $params['propertyName']);
+		// 	if (!is_array($params['propertyName'])) throw new Exception("Wrong param propertyName - expected array or string");
+		// 	foreach ($params['propertyName'] as $fetchField) {
+		// 		if ('*' == $fetchField) continue;
+		// 		if ('field' == $fetchField) {
+		// 			$item['field'] = SchemaFactory::loadDefaultObject('SystemObjectField')->getItems([
+		// 				'__backRef' => [
+		// 					'namespace' => 'default_objects/SystemObject',
+		// 					'primaryKey' => $item['namespace']
+		// 				],
+		// 				'order_by' => 'sortPrio',
+		// 				'order_dir' => 'asc',
+		// 			]);
+		// 		}
+		// 	}
+		// }
+		return $item;
+	}
+
+	// function updateItem($itemPatch) { // @required [ 'namespace' => ... ] (primaryKey)
+	// 	$pkField = $this->getPrimaryKeyField();
+	// 	$pk = V::get($pkField, null, $itemPatch);
+	// 	if (null === $pk) throw new Exception("BUG missing primary key field for '{$this->_namespace}' updateItem");
+	// 	$this->clearGetItemCache($pk);
+	// 	DBG::log(['updateItem $itemPatch', $itemPatch]);
+	// 	unset($itemPatch[$pkField]);
+	// 	if (empty($itemPatch)) return 0;
+	// 	foreach ($itemPatch as $fieldName => $value) {
+	// 		if ('isStructInstalled' == $fieldName) continue;
+	// 		if ('isObjectActive' == $fieldName) continue;
+	// 		if ('primaryKey' == $fieldName) continue;
+	// 		if ('appInfo' == $fieldName) continue;
+	// 		throw new Exception("Update field '{$fieldName}' not allowed for '{$this->_namespace}'");
+	// 	}
+	// 	return DB::getPDO()->update($this->_rootTableName, $pkField, $pk, $itemPatch);
+	// }
+
+	// TODO: to use in AclQueryFeatures
+	function getDB() { return DB::getPDO()->getZasobId(); }
+	function getLocalFieldList() {
+		return array_filter(array_keys($this->_simpleSchema['root']), function ($fieldName) {
+			if ( '@' == substr($fieldName, 0, 1)) return false;
+			return true;
+		});
+	}
+	function hasWriteGroupField() { return $this->_hasWriteGroupField; }
+	function hasReadGroupField() { return $this->_hasReadGroupField; }
+	function hasOwnerField() { return $this->_hasOwnerField; }
+	function isLocalField($fieldName) {
+		return in_array($fieldName, array_keys($this->_simpleSchema['root']));
+	}
+
+	// TODO: to use in AclQueryFeatures
+	function parseSpecialFilter($filterName, $value) { // @return string | array | null
+		switch ($filterName) {
+			case 'UserStatus': return $this->_parseSpecialFilterUserStatus($value);
+			case 'User': return $this->_parseSpecialFilterUser($value);
+			case 'ns': return $this->_parseSpecialFilterNamespace($value);
+			case 'time': return $this->_parseSpecialFilterTime($value);
+			default: return null;
+		}
+	}
+	function _parseSpecialFilterUserStatus($value) {
+		switch ($value) {
+			case 'IS_ACTIVE': return ['USER__IS_ACTIVE', '=', '1'];
+			case 'IS_NOT_ACTIVE': return ['USER__IS_ACTIVE', '=', '0'];
+			default: return null;
+		}
+	}
+	function _parseSpecialFilterNamespace($value) {
+		switch ($value) {
+			case 'IN7_MK_BAZA_DYSTRYBUCJI': return ['namespace', '=', 'default_db/IN7_MK_BAZA_DYSTRYBUCJI'];
+			case 'IN7_DZIENNIK_KORESP': return ['namespace', '=', 'default_db/IN7_DZIENNIK_KORESP'];
+			case 'CRM_PROCES': return ['namespace', '=', 'default_db/CRM_PROCES'];
+			case 'PROBLEMS': return ['namespace', '=', 'default_db/PROBLEMS'];
+			case 'CRM_LISTA_ZASOBOW': return ['namespace', '=', 'default_db/CRM_LISTA_ZASOBOW'];
+			case 'MK_Rewiry': return ['namespace', '=', 'default_db/MK_Rewiry'];
+			case 'BUILDINGS': return ['namespace', '=', 'default_db/BUILDINGS'];
+			case 'QUALITY_NOTICES': return ['namespace', '=', 'default_db/QUALITY_NOTICES'];
+			case 'BADANIA_W_TERENIE': return ['namespace', '=', 'default_db/BADANIA_W_TERENIE'];
+			default: return null;
+		}
+	}
+	function _parseSpecialFilterUser($value) {
+		$login = User::getLogin();
+		switch ($value) {
+			case 'USER': return [ 'L_APPOITMENT_USER', 'or', [
+				[ 'L_APPOITMENT_USER', '=', $login ],
+				[ 'PROJECT__L_APPOITMENT_USER', '=', $login ],
+			]];
+			case 'ALL': return null;
+			default: return null;
+		}
+	}
+	function _parseSpecialFilterTime($value) {
+		switch ($value) {
+			case 'PO_TERMINIE': return ['L_APPOITMENT_DATE', 'and', [
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN_NOW'],
+			] ];
+			case 'DZISIAJ': return ['L_APPOITMENT_DATE', 'and', [
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d"), date("Y"))],
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
+			] ];
+			case 'W_CIAGU_7_DNI': return ['L_APPOITMENT_DATE', 'and', [
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 7, date("Y"))],
+			] ];
+			case 'PO_7_DNIACH': return ['L_APPOITMENT_DATE', 'and', [
+				['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") + 7, date("Y"))],
+			] ];
+			case 'BRAK': return ['L_APPOITMENT_DATE', 'or', [
+				['L_APPOITMENT_DATE', '=', ''],
+				['L_APPOITMENT_DATE', '=', '0000-00-00 00:00:00'],
+			] ];
+			default: return null;
+		}
+	}
+	function getSpecialFilters() {
+		$fltrs = array();
+		{
+			$fltrs['User'] = new stdClass();
+			$fltrs['User']->icon = 'glyphicon glyphicon-user';
+			$fltrs['User']->label = 'Osoba odpowiedzialna';
+			$fltrs['User']->btns = [];
+			$fltrs['User']->btns['Twoje'] = (object)[ 'value' => 'USER' ];
+			$fltrs['User']->btns['Wszyscy'] = (object)[ 'value' => 'ALL' ];
+		}
+		{
+			$fltrs['time'] = new stdClass();
+			$fltrs['time']->icon = 'glyphicon glyphicon-calendar';
+			$fltrs['time']->label = 'Czas';
+			$fltrs['time']->btns = [];
+			$fltrs['time']->btns['po termine'] = (object)[ 'value' => 'PO_TERMINIE' ];
+			$fltrs['time']->btns['dzisiaj'] = (object)[ 'value' => 'DZISIAJ' ];
+			$fltrs['time']->btns['w ciągu 7 dni'] = (object)[ 'value' => 'W_CIAGU_7_DNI' ];
+			$fltrs['time']->btns['po 7 dniach'] = (object)[ 'value' => 'PO_7_DNIACH' ];
+			$fltrs['time']->btns['brak'] = (object)[ 'value' => 'BRAK' ];
+		}
+		{
+			$fltrs['ns'] = new stdClass();
+			$fltrs['ns']->icon = 'glyphicon glyphicon-tag';
+			$fltrs['ns']->label = 'Typ rekordu';
+			$fltrs['ns']->btns = [];
+			$fltrs['ns']->btns['Projekty'] = (object)[ 'value' => 'IN7_MK_BAZA_DYSTRYBUCJI' ];
+			$fltrs['ns']->btns['Koresp.'] = (object)[ 'value' => 'IN7_DZIENNIK_KORESP' ];
+			$fltrs['ns']->btns['Procesy'] = (object)[ 'value' => 'CRM_PROCES' ];
+			$fltrs['ns']->btns['Zadania'] = (object)[ 'value' => 'PROBLEMS' ];
+			$fltrs['ns']->btns['Zasoby'] = (object)[ 'value' => 'CRM_LISTA_ZASOBOW' ];
+			$fltrs['ns']->btns['MK_Rewiry'] = (object)[ 'value' => 'MK_Rewiry' ];
+			$fltrs['ns']->btns['Budynki'] = (object)[ 'value' => 'BUILDINGS' ];
+			$fltrs['ns']->btns['Jakość-Zgłoszenia'] = (object)[ 'value' => 'QUALITY_NOTICES' ];
+			$fltrs['ns']->btns['Badania w terenie'] = (object)[ 'value' => 'BADANIA_W_TERENIE' ];
+		}
+		{
+			$fltrs['UserStatus'] = new stdClass();
+			$fltrs['UserStatus']->icon = 'glyphicon glyphicon-user';
+			$fltrs['UserStatus']->label = 'Status użytkownika';
+			$fltrs['UserStatus']->btns = [];
+			$fltrs['UserStatus']->btns['Aktywny'] = (object)[ 'value' => 'IS_ACTIVE' ];
+			$fltrs['UserStatus']->btns['Nieaktywny'] = (object)[ 'value' => 'IS_NOT_ACTIVE' ];
+		}
+		return $fltrs;
+	}
+
+	function getGuiRowFunctions() { // @return array
+		return [
+			'row_edit' => [ 'href' => Router::getRoute('ViewTableAjax')->getLink('executeRowFunction', [ 'name' => "edit", 'namespace' => $this->getNamespace(), 'pk' => "{0}" ]),
+				'ico' => 'glyphicon glyphicon-pencil',
+				'title' => 'Edytuj powiązany rekord',
+				'class' => "btn btn-xs btn-link"
+			],
+			'project_edit' => [ 'href' => Router::getRoute('ViewTableAjax')->getLink('executeRowFunction', [ 'name' => "project", 'namespace' => $this->getNamespace(), 'pk' => "{0}" ]),
+				'ico' => 'glyphicon glyphicon-folder-close',
+				'title' => 'Edytuj powiązany projekt',
+				'class' => "btn btn-xs btn-link"
+			],
+		];
+	}
+	function executeGuiRowFunction($name, $pk) {
+		switch ($name) {
+			case 'edit': $this->executeGuiRowFunctionEdit($pk);
+			case 'project': $this->executeGuiRowFunctionProject($pk);
+			default: throw new Exception("Not implemented GuiRowFunction name='{$name}' for '" . $this->getNamespace() . "'");
+		}
+	}
+	function executeGuiRowFunctionEdit($pk) {
+		list($tableName, $id) = explode(".", $pk);
+		Lib::loadClass('Response');
+		Response::sendRedirect( Router::getRoute('ViewTableAjax')->getLink('', [ 'namespace' => "default_db/{$tableName}" ]) . "#EDIT/{$id}" );
+	}
+	function executeGuiRowFunctionProject($pk) {
+		list($tableName, $id) = explode(".", $pk);
+		$idProject = DB::getPDO()->fetchValue(" select PROJECT__ID from `_PRZYPOMNIJ_ITEMS` where feature_id = :feature_id ", [ ':feature_id' => $pk ]);
+		if (!$idProject) throw new Exception("Brak przypisanego projektu");
+		Lib::loadClass('Response');
+		Response::sendRedirect( Router::getRoute('ViewTableAjax')->getLink('', [ 'namespace' => "default_db/IN7_MK_BAZA_DYSTRYBUCJI" ]) . "#EDIT/{$idProject}" );
+	}
+
+}

+ 35 - 0
SE/se-lib/StorageAclBase.php

@@ -0,0 +1,35 @@
+<?php
+
+class StorageAclBase {
+
+	static function buildInstance($idZasob, $conf = []) {
+		static $_cache;
+		if (!$_cache) $_cache = array();
+		if (array_key_exists($idZasob, $_cache)) {
+			return $_cache[$idZasob];
+		}
+
+		if (empty($conf)) throw new Exception("Brak danych konfiguracyjnych do obiektu StorageAcl nr {$idZasob}");
+		DBG::log($conf, 'array', 'AntAclBase::buildInstance $conf');
+		if (empty($conf['name'])) throw new Exception("Błędne dane konfiguracyjne do obiektu StorageAcl nr {$idZasob}: brak nazwy");
+
+		$className = "Schema_{$conf['name']}StorageAcl";
+		Lib::loadClass($className);
+		$acl = new $className($idZasob);
+		$acl->_zasobID = (int)$idZasob;
+		$acl->_name = $conf['name'];
+		$acl->_rootTableName = $conf['_rootTableName'];
+		$acl->_db = $conf['idDatabase'];
+		$acl->_namespace = $conf['namespace'];
+		$acl->_rootNamespace = str_replace('__x3A__', '/', $conf['nsPrefix']);
+		$acl->_fields = $conf['field']; // TODO: lazyLoading - use getFields() in all functions - TODO: use ACL::getObjectFields
+		$acl->_primaryKey = (!empty($conf['primaryKey'])) ? $conf['primaryKey'] : 'ID'; // $conf['primaryKey'];
+		$acl->_hasWriteGroupField = $conf['hasWriteGroupField'];
+		$acl->_hasReadGroupField = $conf['hasReadGroupField'];
+		$acl->_hasOwnerField = $conf['hasOwnerField'];
+
+		$_cache[$idZasob] = $acl;
+		return $_cache[$idZasob];
+	}
+
+}

+ 25 - 12
SE/se-lib/TableAcl.php

@@ -953,19 +953,32 @@ class TableAcl extends Core_AclBase {
 
 				$objItem = reset($objectList);
 				DBG::log($objItem, 'array', "DBG objItem({$idTable})");
-				if (!in_array($objItem['_type'], [
-					// 'TableAcl', // TODO: TEST - to replace TableAcl by AntAcl or use object with namespace + '/tableName'?
-					'AntAcl',
-				])) throw new Exception("Not Implemented acl type '{$objItem['_type']}'");
-				if (!$objItem['isObjectActive']) {
-					if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
-					if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
-					throw new Exception("namespace is not activated '{$namespace}'");
-				}
+				switch ($objItem['_type']) {
+					// case 'TableAcl': // TODO: TEST - to replace TableAcl by AntAcl or use object with namespace + '/tableName'?
+					case 'AntAcl': {
+						if (!$objItem['isObjectActive']) {
+							if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
+							if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
+							throw new Exception("namespace is not activated '{$namespace}'");
+						}
 
-				Lib::loadClass('AntAclBase');
-				$_cache[$idTable] = AntAclBase::buildInstance($objItem['idZasob'], $objItem);
-				return $_cache[$idTable];
+						Lib::loadClass('AntAclBase');
+						$_cache[$idTable] = AntAclBase::buildInstance($objItem['idZasob'], $objItem);
+						return $_cache[$idTable];
+					}
+					case 'StorageAcl': {
+						if (!$objItem['isObjectActive']) {
+							if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
+							if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
+							throw new Exception("namespace is not activated '{$namespace}'");
+						}
+
+						Lib::loadClass('StorageAclBase');
+						$_cache[$idTable] = StorageAclBase::buildInstance($objItem['idZasob'], $objItem);
+						return $_cache[$idTable];
+					}
+					default: throw new Exception("Not Implemented acl type '{$objItem['_type']}'");
+				}
 			} catch (Exception $e) {
 				DBG::log($e);
 			}

+ 3 - 129
SE/se-lib/TableAjax.php

@@ -259,134 +259,7 @@ class TableAjax extends ViewAjax {
 		echo UI::h('script', ['src'=>"static/jquery.doubleScroll.js"]);
 		echo UI::h('link', ['rel'=>"stylesheet", 'type'=>"text/css", 'href'=>"stuff/jquery-ui-smoothness/jquery-ui-1.10.4.custom.min.css"]);
 		echo UI::h('link', ['rel'=>"stylesheet", 'type'=>"text/css", 'href'=>"static/sweetalert2.min.css"]);
-		echo UI::h('style', ['type'=>"text/css"], "
-			.AjaxTable{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
-
-			.AjaxTableCont {position:relative; margin:10px 0; width:100%; overflow-x:auto; border:none;}
-			.AjaxTable *,
-			.AjaxTable.table {white-space:nowrap; width:auto;}
-			.AjaxTable .popover * {white-space:normal;}
-			.AjaxTable p {margin:0;}
-			.AjaxTable td, .AjaxTable th {line-height:18px;}
-			.AjaxTable i {margin: 0 0 0 2px;opacity: 0.5;}
-			.AjaxTable span.filter {background-color:#999;}
-			.AjaxTable .indeterminate {opacity: 0.4;-ms-filter:\"progid:DXImageTransform.Microsoft.Alpha(Opacity=40)\";filter:alpha(opacity=40);}
-			.AjaxTable .columnpicker li, .AjaxTable .actions li {padding-left:5px;}
-			.AjaxTableCont .btn-toolbar {margin:2px 0 0 0;}
-			.AjaxTableCont .btn-toolbar .btn-group {vertical-align:top;}
-			.AjaxTable input[type=checkbox] {margin:0;padding:0;height:13px;}
-			.AjaxTable input[type=text].filter {margin:0;padding:0 5px;box-shadow:none;width:100%;background:#eee;color:#000;}
-			.AjaxTable input[type=text].filter {border-width:2px 0;border-color:#eee;border-style:solid;}
-			.AjaxTable input[type=text].filter-active {border-color:#00ACCC;}
-			.AjaxTable .date-wrap {width:100%;min-width:115px;}
-			.AjaxTable .dateWrap .add-on {margin:0;padding:0;background:none;border:none;float:right;cursor:pointer;}
-			.AjaxTable .actions a {padding:0;margin:0;}
-			.AjaxTableCont .text-right {text-align:right}
-			.AjaxTableCont .breadcrumb em {color:#bbb;}
-
-			.AjaxTable-loading .head-info {background:url(./icon/loading.gif) no-repeat left top; background-position:0 6px;}
-			.AjaxTable-loading .loading-info,
-			.AjaxTableCont .tblAjax__inlineEditBox .loading-info {display:block; padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;}
-			.AjaxTableCont .loading-info {display:none;}
-			.AjaxTableCont .tblAjax__inlineEditBox .loading-info {display:block;}
-			.AjaxTableCont .table {margin-bottom:0px;}
-			.AjaxTableCont .stickyCol1 a { color:#333; }
-			.AjaxTableCont .stickyCol1 a:hover { color:#337AB7; text-decoration:none; }
-			.AjaxTableTaskCnt {padding:0 10px 10px 10px;}
-
-			.AjaxTable .tbl-short-txt{max-width:140px; overflow:hidden;}
-			.AjaxTable .tbl-short-txt span{display:block; height:18px;}
-			.AjaxTable .tooltip-inner{white-space:normal;}
-			.AjaxTable thead .sort .ta-ordering {cursor:pointer;}
-			.AjaxTable thead .sort .ta-ordering span {margin:0 10px 0 0; padding:0; background-repeat:no-repeat; background-position:right top;}
-			.AjaxTable thead .sort .ta-ordering-down span:after{content:\"\\e252\";font-family:\"Glyphicons Halflings\";line-height:1;margin:0 0 0 3px;display:inline-block;color:#bbb;}
-			.AjaxTable thead .sort .ta-ordering-up   span:after{content:\"\\e253\";font-family:\"Glyphicons Halflings\";line-height:1;margin:0 0 0 3px;display:inline-block;color:#bbb;}
-			.AjaxTable thead .sort .ta-ordering {position:relative;}
-
-			.AjaxTable thead .sort th .hover-show:hover{background-color:#000; background-image:url(stuff/twitter-bootstrap/img/glyphicons-halflings-white.png);}
-			.AjaxTable thead .sort th .hover-show{display:none;}
-			.AjaxTable thead .sort th:hover .hover-show{display:inline;}
-
-			.AjaxTable thead .sort .ta-ordering .remove-cell {position:absolute; top:6px; right:2px; display:none; color:#f00;}
-			.AjaxTable thead .sort .ta-ordering:hover .remove-cell {display:block;}
-
-			 .AjaxTableCont .pagination { margin:0; }
-			 .AjaxTableCont .pagination a { line-height:16px; }
-
-			.AjaxTableCont .AjaxTableEdit-label { display:block; margin:0 0 3px 0; font-size:12px !important; line-height:16px !important; }
-			.AjaxTableCont .AjaxTableEdit-label code { padding:0; white-space:nowrap; background-color:transparent; border:none; color:#777; font-size:10px !important; line-height:14px !important; }
-			.AjaxTableCont .AjaxTableEdit .show-last-value .button-appendBack .glyphicon,
-			.AjaxTableCont .AjaxFrmHorizontalEdit .show-last-value .button-appendBack .glyphicon {display:none;}
-			.AjaxTableHist em {color:silver;}
-
-			.AjaxTableCont .foot * { font-size:12px !important; }
-			.AjaxTableCont .foot { margin:10px; }
-			.AjaxTableCont .foot .foot-info { float:left; padding:0 20px; }
-			 .AjaxTableCont .foot .foot-info p { line-height:16px; margin:5px 0; }
-			.AjaxTable-loading .foot .foot-info {padding-left:20px; background:url(./icon/loading.gif) no-repeat left top;}
-
-			.tblAjax__head__specialFilter {margin:0;padding:4px 4px 4px 127px;}
-			.tblAjax__head__specialFilter .btn-group {margin:0 4px;padding:0;}
-			.tblAjax__head__specialFilter .btn-group .glyphicon-remove {color:#f00;}
-			.tblAjax__head__specialFilter .btn-group button.disabled .glyphicon-remove {color:#bbb;}
-
-			/* overwrite bootstrap table border */
-			.AjaxTable,    .AjaxTableEdit,
-			.AjaxTable td, .AjaxTableEdit td,
-			.AjaxTable th, .AjaxTableEdit th { border-color:#999; }
-
-			/* cell A_STATUS */
-			.AjaxTable .cell-A_STATUS-NORMAL { background:#aeffae; color:#000; text-align:center; }
-			.AjaxTable .cell-A_STATUS-WAITING { background:#ffd2ff; color:#000; text-align:center; }
-			.AjaxTable .cell-A_STATUS-MONITOR { background:#cccaff; color:#000; text-align:center; }
-			.AjaxTable .cell-A_STATUS-WARNING { background:#ffbaba; color:#000; text-align:center; }
-			.AjaxTable .cell-A_STATUS-DELETED { background:#e0e0e0; color:#808080; text-align:center; }
-			.AjaxTable .cell-A_STATUS-OFF_SOFT { background:#fce3b7; color:#808080; text-align:center; }
-			.AjaxTable .cell-A_STATUS-OFF_HARD { background:#eee; color:#808080; text-align:center; }
-
-			/* cell Status */
-			.AjaxTable .cell-Status-U  { background:rgb(0,176,80); color:#000; text-align:center; }
-			.AjaxTable .cell-Status-NU { background:#f00; color:#000; text-align:center; }
-			.AjaxTable .cell-Status-P  { background:rgb(112,48,160); color:#000; text-align:center; }
-			.AjaxTable .cell-Status-PT { background:rgb(127,127,127); color:#000; text-align:center; }
-			.AjaxTable .cell-Status-O  { background:rgb(0,176,240); color:#000; text-align:center; }
-			.AjaxTable .cell-Status-DZ { background:rgb(0,112,192); color:#000; text-align:center; }
-			.AjaxTable .cell-Status-Z  {}
-
-			/* map */
-			.AjaxTable .cell-mapfld { cursor:pointer; }
-			.AjaxTable .cell-mapfld:hover { opacity:1; }
-			 .AjaxTable .cell-mapfld-remove { display:none; }
-			 .AjaxTable .cell-mapfld-hasValue .cell-mapfld-select { color:#f00; }
-			 .AjaxTable .cell-mapfld-hasValue .cell-mapfld-remove { display:inline-block; }
-			.AjaxTableCont .mapEditor { position:absolute; bottom:0; right:6px; width:512px; height:318px; overflow:hidden; color:#eee; border:1px solid #999; }
-			 .AjaxTableCont .mapEditor-panel { height:16px; padding:0 6px; background:#999; border-bottom:1px solid #eee; }
-			  .AjaxTableCont .mapEditor-panel a { display:block; float:right; padding:0 6px; font-weight:bold; font-size:12px; line-height:16px; color:#fff; }
-			  .AjaxTableCont .mapEditor-panel a:hover { color:#006CD7; text-decoration:none; }
-			  .AjaxTableCont .mapEditor-panel a.mapEditor-panel-close:hover { color:#f00; }
-			 .AjaxTableCont .mapEditor-map { background:#fff; height:400px; }
-			.AjaxTableCont-mapEditorContainer .mapEditor-map { border:1px solid #999; overflow:hidden; }
-			/* .mapEditor-btnBackToWindow \"olControlSave\",overview_replacement */
-			.olControlEditingToolbar .mapEditor-btnBackToWindowItemInactive,
-			.olControlEditingToolbar .mapEditor-btnBackToWindowItemActive {
-				background-image: url(icon/map.window.png);
-				background-position: 0 0;
-				background-repeat: no-repeat;
-			}
-			/*
-			.mapEditor-btnBackToWindowItemInactive { background-image: url(stuff/open-layers/theme/default/img/overview_replacement.gif); }
-			.mapEditor-btnBackToWindowItemActive { background-image: url(stuff/open-layers/theme/default/img/overview_replacement.gif); }
-			*/
-			.ui-dialog-content .mapEditor-btnBackToWindowItemInactive,
-			.ui-dialog-content .mapEditor-btnBackToWindowItemActive { display:none; }
-
-			.AjaxTableCont .valign-btns-bottom .btn { vertical-align:text-bottom; font-weight:normal; font-size:12px; line-height:14px; }
-
-			.ui-resizable-s { bottom:0; }
-			.ui-resizable-e { right:0; }
-			.AjaxTableCont-mapEditorContainer .ui-resizable-s { background-color:#ddd; }
-			.AjaxTableCont-mapEditorContainer .ui-resizable-s:hover { background-color:#888; }
-		");
+		UI::inlineCSS( __FILE__ . '.style.css' );
 		echo UI::h('script', [ 'src' => "static/vendor.js" ]); // window.p5VendorJs: {React, ReactDOM, createReactClass, Redux}
 		echo UI::h('script', [ 'src' => "static/p5UI/buildDom.js" ]);
 		$_rendered = true;
@@ -2095,7 +1968,7 @@ jQuery(document).ready(function(){
 		exit;
 	}
 
-	private function sendTypeSpecial($fldID, $args) {
+	private function sendTypeSpecial($fldID, $args) { // TODO: mv to index.php?_route=ViewTableAjax&_task=typespecial&idField=" + idField, // &q=... or &selected=... or &idRecord=...
 		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
 
 		header("Content-type: application/json");
@@ -2352,6 +2225,7 @@ jQuery(document).ready(function(){
 		$listItems = $queryFeatures->getItems();
 		$primaryKeyField = $acl->getPrimaryKeyField();
 		$items = []; foreach ($listItems as $item) $items[ $item[$primaryKeyField] ] = $item;
+
 		// TODO: add virtual data by Typespecial
 		if (!empty($vCols) && !empty($items)) {
 			foreach ($vCols as $vColID => $vCol) {

+ 86 - 87
SE/se-lib/TableAjax.php.TableAjax.js

@@ -742,99 +742,98 @@ var TableAjax = function() {
 	priv.popoverCellMoreFunctions = function(e) {
 		e.preventDefault();
 		e.stopPropagation();
+		if (!e.data || !e.data.rowPK) {
+			if (priv.options.debug) console.log('NO data');
+			return false;
+		}
 		if (priv.options.debug) console.log('TableAjax::popoverCellMoreFunctions: rowPK', e.data.rowPK, 'moreFunctions', e.data.more, 'rowFunctions', e.data.rowFunctions);
-		if ('rowPK' in e.data && e.data.rowPK > 0) {
-			var lastId = _popoverCell.data('rowid'),
-					lastCol = _popoverCell.data('col'),
-					rowPK = e.data.rowPK,
-					moreFunctions = e.data.more,
-					rowFunctions = e.data.rowFunctions,
-					funcListNode
-			;
-
-			if (lastId == e.data.rowPK && lastCol == 'moreFunctions') {
-				//_popoverCellCurrent.popover('toggle');
-			}
-			else {
-				if (_popoverCellCurrent) {
-					_popoverCellCurrent.popover('destroy');
-				}
+		var lastId = _popoverCell.data('rowid');
+		var lastCol = _popoverCell.data('col');
+		var rowPK = e.data.rowPK;
+		var moreFunctions = e.data.more;
+		var rowFunctions = e.data.rowFunctions;
+		var funcListNode;
+
+		if (lastId == e.data.rowPK && lastCol == 'moreFunctions') {
+			//_popoverCellCurrent.popover('toggle');
+			return;
+		}
 
-				_popoverCell.data('rowid', rowPK);
-				_popoverCell.data('col', 'rowFunctions');
-				_popoverCell.empty();
-				funcListNode = $('<ul class="list-unstyled popoverRowFunctions"></ul>').appendTo(_popoverCell);
-
-				moreFunctions.forEach(function(funcName) {
-					var funcItemNode = $('<li></li>').appendTo(funcListNode),
-							funObj = rowFunctions[funcName],
-							funcNode = p5UI_TableAjax_generateFunctionNode(funObj, rowPK, {ico: true, label: true})
-					;
-					funcNode.addClass('func_name-' + funcName);
-					funcItemNode.append(funcNode);
-				});
-				{ // TODO: id namesapce is AntAcl [ and has ref fields or back ref ]
-					var funcItemNode = $('<li></li>').appendTo(funcListNode)
-					var funObj = {
-						ico: 'glyphicon glyphicon-random',
-						label: 'Przeglądaj powiązania',
-						href: 'index.php?_route=RefGraph&namespace=' + priv.options.namespace + '&primaryKey=' + rowPK,
-						// onclick: function (e) { // TODO: open in
-						// 	console.log('TODO: przeglądaj powiązania')
-						// }
-					}
-					var funcNode = p5UI_TableAjax_generateFunctionNode(funObj, rowPK, { ico: true, label: true })
-					funcItemNode.append(funcNode);
-				}
+		if (_popoverCellCurrent) {
+			_popoverCellCurrent.popover('destroy');
+		}
 
-				_popoverCell.append('<div class="popoverCellContent" style="white-space:normal"></div>');
+		_popoverCell.data('rowid', rowPK);
+		_popoverCell.data('col', 'rowFunctions');
+		_popoverCell.empty();
+		funcListNode = $('<ul class="list-unstyled popoverRowFunctions"></ul>').appendTo(_popoverCell);
 
-				_popoverCellCurrent = jQuery(e.currentTarget);
-				// title : '<span class="text-info"><strong>title</strong></span> <button type="button" id="close" class="close">&times;</button>'
-				var opts = {
-					container: 'body',
-					placement: 'right',
-					trigger: 'click',
-					template: '',
-					html: true,
-					content: _popoverCell.html()
-				}
-				opts.template += '<div class="popover" role="tooltip" style="max-width:600px;width:440px;">';
-				{
-					opts.template += '<div class="arrow"></div>';
-					//opts.template += '<h3 class="popover-title"></h3>';
-					opts.template += '<div style="display:block;position:relative;">';
-					{
-						opts.template += '<div class="popover-title">';
-						opts.template += '</div>';
-						opts.template += '<button type="button" class="close" onclick="return hidePopover();" style="position:absolute;right:8px;top:6px;">&times;</button>';
-					}
-					opts.template += '</div>';
-					opts.template += '<div class="popover-content"></div>';
-				}
+		moreFunctions.forEach(function(funcName) {
+			var funcItemNode = $('<li></li>').appendTo(funcListNode),
+					funObj = rowFunctions[funcName],
+					funcNode = p5UI_TableAjax_generateFunctionNode(funObj, rowPK, {ico: true, label: true})
+			;
+			funcNode.addClass('func_name-' + funcName);
+			funcItemNode.append(funcNode);
+		});
+		if (3 === priv.options.namespace.split('/').length) { // if namesapce is AntAcl [ and has ref fields or back ref ]
+			var funcItemNode = $('<li></li>').appendTo(funcListNode)
+			var funObj = {
+				ico: 'glyphicon glyphicon-random',
+				label: 'Przeglądaj powiązania',
+				href: 'index.php?_route=RefGraph&namespace=' + priv.options.namespace + '&primaryKey=' + rowPK,
+				// onclick: function (e) { // TODO: open in
+				// 	console.log('TODO: przeglądaj powiązania')
+				// }
+			}
+			var funcNode = p5UI_TableAjax_generateFunctionNode(funObj, rowPK, { ico: true, label: true })
+			funcItemNode.append(funcNode);
+		}
+
+		_popoverCell.append('<div class="popoverCellContent" style="white-space:normal"></div>');
+
+		_popoverCellCurrent = jQuery(e.currentTarget);
+		// title : '<span class="text-info"><strong>title</strong></span> <button type="button" id="close" class="close">&times;</button>'
+		var opts = {
+			container: 'body',
+			placement: 'right',
+			trigger: 'click',
+			template: '',
+			html: true,
+			content: _popoverCell.html()
+		}
+		opts.template += '<div class="popover" role="tooltip" style="max-width:600px;width:440px;">';
+		{
+			opts.template += '<div class="arrow"></div>';
+			//opts.template += '<h3 class="popover-title"></h3>';
+			opts.template += '<div style="display:block;position:relative;">';
+			{
+				opts.template += '<div class="popover-title">';
 				opts.template += '</div>';
+				opts.template += '<button type="button" class="close" onclick="return hidePopover();" style="position:absolute;right:8px;top:6px;">&times;</button>';
+			}
+			opts.template += '</div>';
+			opts.template += '<div class="popover-content"></div>';
+		}
+		opts.template += '</div>';
+
+		_popoverCellCurrent.popover(opts);
+		_popoverCellCurrent.on('shown.bs.popover', function(e) {
+			if (!_popoverCellCurrent) return;
+			var popoverNodeId = _popoverCellCurrent.attr('aria-describedby');
+			if (!popoverNodeId) return;
+			var popover$Node = jQuery('#' + popoverNodeId);
+			if (!popover$Node.length) return;
+			var arrow$Node = popover$Node.children('.arrow');
+			if (arrow$Node.length) {
+				var posTop = parseFloat(arrow$Node.css('top'));
+				arrow$Node.css('top', '' + posTop + 'px')
+			}
+			popover$Node.find('.popoverCellContent').html('<p class="text-muted">Pobieranie funkcji...<p>');
+			priv.ajaxLoadMoreFunctionsCell(rowPK);
+		});
+		_popoverCellCurrent.popover('show');
 
-				_popoverCellCurrent.popover(opts);
-				_popoverCellCurrent.on('shown.bs.popover', function(e) {
-					if (!_popoverCellCurrent) return;
-					var popoverNodeId = _popoverCellCurrent.attr('aria-describedby');
-					if (!popoverNodeId) return;
-					var popover$Node = jQuery('#' + popoverNodeId);
-					if (!popover$Node.length) return;
-					var arrow$Node = popover$Node.children('.arrow');
-					if (arrow$Node.length) {
-						var posTop = parseFloat(arrow$Node.css('top'));
-						arrow$Node.css('top', '' + posTop + 'px')
-					}
-					popover$Node.find('.popoverCellContent').html('<p class="text-muted">Pobieranie funkcji...<p>');
-					priv.ajaxLoadMoreFunctionsCell(rowPK);
-				});
-				_popoverCellCurrent.popover('show');
-			}
-		} else {
-			if (priv.options.debug) console.log('NO data');
-			return false;
-		}
 		return;
 	};
 

+ 126 - 0
SE/se-lib/TableAjax.php.style.css

@@ -0,0 +1,126 @@
+.AjaxTable{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
+
+.AjaxTableCont {position:relative; margin:10px 0; width:100%; overflow-x:auto; border:none;}
+.AjaxTable *,
+.AjaxTable.table {white-space:nowrap; width:auto;}
+.AjaxTable .popover * {white-space:normal;}
+.AjaxTable p {margin:0;}
+.AjaxTable td, .AjaxTable th {line-height:18px;}
+.AjaxTable i {margin: 0 0 0 2px;opacity: 0.5;}
+.AjaxTable span.filter {background-color:#999;}
+.AjaxTable .indeterminate {opacity: 0.4;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";filter:alpha(opacity=40);}
+.AjaxTable .columnpicker li, .AjaxTable .actions li {padding-left:5px;}
+.AjaxTableCont .btn-toolbar {margin:2px 0 0 0;}
+.AjaxTableCont .btn-toolbar .btn-group {vertical-align:top;}
+.AjaxTable input[type=checkbox] {margin:0;padding:0;height:13px;}
+.AjaxTable input[type=text].filter {margin:0;padding:0 5px;box-shadow:none;width:100%;background:#eee;color:#000;}
+.AjaxTable input[type=text].filter {border-width:2px 0;border-color:#eee;border-style:solid;}
+.AjaxTable input[type=text].filter-active {border-color:#00ACCC;}
+.AjaxTable .date-wrap {width:100%;min-width:115px;}
+.AjaxTable .dateWrap .add-on {margin:0;padding:0;background:none;border:none;float:right;cursor:pointer;}
+.AjaxTable .actions a {padding:0;margin:0;}
+.AjaxTableCont .text-right {text-align:right}
+.AjaxTableCont .breadcrumb em {color:#bbb;}
+
+.AjaxTable-loading .head-info {background:url(./icon/loading.gif) no-repeat left top; background-position:0 6px;}
+.AjaxTable-loading .loading-info,
+.AjaxTableCont .tblAjax__inlineEditBox .loading-info {display:block; padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;}
+.AjaxTableCont .loading-info {display:none;}
+.AjaxTableCont .tblAjax__inlineEditBox .loading-info {display:block;}
+.AjaxTableCont .table {margin-bottom:0px;}
+.AjaxTableCont .stickyCol1 a { color:#333; }
+.AjaxTableCont .stickyCol1 a:hover { color:#337AB7; text-decoration:none; }
+.AjaxTableTaskCnt {padding:0 10px 10px 10px;}
+
+.AjaxTable .tbl-short-txt{max-width:140px; overflow:hidden;}
+.AjaxTable .tbl-short-txt span{display:block; height:18px;}
+.AjaxTable .tooltip-inner{white-space:normal;}
+.AjaxTable thead .sort .ta-ordering {cursor:pointer;}
+.AjaxTable thead .sort .ta-ordering span {margin:0 10px 0 0; padding:0; background-repeat:no-repeat; background-position:right top;}
+.AjaxTable thead .sort .ta-ordering-down span:after{content:"\e252";font-family:"Glyphicons Halflings";line-height:1;margin:0 0 0 3px;display:inline-block;color:#bbb;}
+.AjaxTable thead .sort .ta-ordering-up   span:after{content:"\e253";font-family:"Glyphicons Halflings";line-height:1;margin:0 0 0 3px;display:inline-block;color:#bbb;}
+.AjaxTable thead .sort .ta-ordering {position:relative;}
+
+.AjaxTable thead .sort th .hover-show:hover{background-color:#000; background-image:url(stuff/twitter-bootstrap/img/glyphicons-halflings-white.png);}
+.AjaxTable thead .sort th .hover-show{display:none;}
+.AjaxTable thead .sort th:hover .hover-show{display:inline;}
+
+.AjaxTable thead .sort .ta-ordering .remove-cell {position:absolute; top:6px; right:2px; display:none; color:#f00;}
+.AjaxTable thead .sort .ta-ordering:hover .remove-cell {display:block;}
+
+ .AjaxTableCont .pagination { margin:0; }
+ .AjaxTableCont .pagination a { line-height:16px; }
+
+.AjaxTableCont .AjaxTableEdit-label { display:block; margin:0 0 3px 0; font-size:12px !important; line-height:16px !important; }
+.AjaxTableCont .AjaxTableEdit-label code { padding:0; white-space:nowrap; background-color:transparent; border:none; color:#777; font-size:10px !important; line-height:14px !important; }
+.AjaxTableCont .AjaxTableEdit .show-last-value .button-appendBack .glyphicon,
+.AjaxTableCont .AjaxFrmHorizontalEdit .show-last-value .button-appendBack .glyphicon {display:none;}
+.AjaxTableHist em {color:silver;}
+
+.AjaxTableCont .foot * { font-size:12px !important; }
+.AjaxTableCont .foot { margin:10px; }
+.AjaxTableCont .foot .foot-info { float:left; padding:0 20px; }
+ .AjaxTableCont .foot .foot-info p { line-height:16px; margin:5px 0; }
+.AjaxTable-loading .foot .foot-info {padding-left:20px; background:url(./icon/loading.gif) no-repeat left top;}
+
+.tblAjax__head__specialFilter {margin:0;padding:4px 4px 0 127px;}
+.tblAjax__head__specialFilter .btn-group {margin:0 4px 4px 4px;padding:0;}
+.tblAjax__head__specialFilter .btn-group .glyphicon-remove {color:#f00;}
+.tblAjax__head__specialFilter .btn-group button.disabled .glyphicon-remove {color:#bbb;}
+
+/* overwrite bootstrap table border */
+.AjaxTable,    .AjaxTableEdit,
+.AjaxTable td, .AjaxTableEdit td,
+.AjaxTable th, .AjaxTableEdit th { border-color:#999; }
+
+/* cell A_STATUS */
+.AjaxTable .cell-A_STATUS-NORMAL { background:#aeffae; color:#000; text-align:center; }
+.AjaxTable .cell-A_STATUS-WAITING { background:#ffd2ff; color:#000; text-align:center; }
+.AjaxTable .cell-A_STATUS-MONITOR { background:#cccaff; color:#000; text-align:center; }
+.AjaxTable .cell-A_STATUS-WARNING { background:#ffbaba; color:#000; text-align:center; }
+.AjaxTable .cell-A_STATUS-DELETED { background:#e0e0e0; color:#808080; text-align:center; }
+.AjaxTable .cell-A_STATUS-OFF_SOFT { background:#fce3b7; color:#808080; text-align:center; }
+.AjaxTable .cell-A_STATUS-OFF_HARD { background:#eee; color:#808080; text-align:center; }
+
+/* cell Status */
+.AjaxTable .cell-Status-U  { background:rgb(0,176,80); color:#000; text-align:center; }
+.AjaxTable .cell-Status-NU { background:#f00; color:#000; text-align:center; }
+.AjaxTable .cell-Status-P  { background:rgb(112,48,160); color:#000; text-align:center; }
+.AjaxTable .cell-Status-PT { background:rgb(127,127,127); color:#000; text-align:center; }
+.AjaxTable .cell-Status-O  { background:rgb(0,176,240); color:#000; text-align:center; }
+.AjaxTable .cell-Status-DZ { background:rgb(0,112,192); color:#000; text-align:center; }
+.AjaxTable .cell-Status-Z  {}
+
+/* map */
+.AjaxTable .cell-mapfld { cursor:pointer; }
+.AjaxTable .cell-mapfld:hover { opacity:1; }
+ .AjaxTable .cell-mapfld-remove { display:none; }
+ .AjaxTable .cell-mapfld-hasValue .cell-mapfld-select { color:#f00; }
+ .AjaxTable .cell-mapfld-hasValue .cell-mapfld-remove { display:inline-block; }
+.AjaxTableCont .mapEditor { position:absolute; bottom:0; right:6px; width:512px; height:318px; overflow:hidden; color:#eee; border:1px solid #999; }
+ .AjaxTableCont .mapEditor-panel { height:16px; padding:0 6px; background:#999; border-bottom:1px solid #eee; }
+	.AjaxTableCont .mapEditor-panel a { display:block; float:right; padding:0 6px; font-weight:bold; font-size:12px; line-height:16px; color:#fff; }
+	.AjaxTableCont .mapEditor-panel a:hover { color:#006CD7; text-decoration:none; }
+	.AjaxTableCont .mapEditor-panel a.mapEditor-panel-close:hover { color:#f00; }
+ .AjaxTableCont .mapEditor-map { background:#fff; height:400px; }
+.AjaxTableCont-mapEditorContainer .mapEditor-map { border:1px solid #999; overflow:hidden; }
+/* .mapEditor-btnBackToWindow "olControlSave",overview_replacement */
+.olControlEditingToolbar .mapEditor-btnBackToWindowItemInactive,
+.olControlEditingToolbar .mapEditor-btnBackToWindowItemActive {
+	background-image: url(icon/map.window.png);
+	background-position: 0 0;
+	background-repeat: no-repeat;
+}
+/*
+.mapEditor-btnBackToWindowItemInactive { background-image: url(stuff/open-layers/theme/default/img/overview_replacement.gif); }
+.mapEditor-btnBackToWindowItemActive { background-image: url(stuff/open-layers/theme/default/img/overview_replacement.gif); }
+*/
+.ui-dialog-content .mapEditor-btnBackToWindowItemInactive,
+.ui-dialog-content .mapEditor-btnBackToWindowItemActive { display:none; }
+
+.AjaxTableCont .valign-btns-bottom .btn { vertical-align:text-bottom; font-weight:normal; font-size:12px; line-height:14px; }
+
+.ui-resizable-s { bottom:0; }
+.ui-resizable-e { right:0; }
+.AjaxTableCont-mapEditorContainer .ui-resizable-s { background-color:#ddd; }
+.AjaxTableCont-mapEditorContainer .ui-resizable-s:hover { background-color:#888; }

+ 9 - 0
SE/se-lib/UI.php

@@ -819,6 +819,15 @@ class UI {
 		if ($params['showContainer']) UI::startContainer( $params['containerClass'] ? [ 'class' => $params['containerClass'] ] : [] );
 		try {
 			call_user_func($callback);
+		} catch (AlertSuccessException $e) {
+			DBG::log($e);
+			UI::alert('success', $e->getMessage());
+		} catch (AlertWarningException $e) {
+			DBG::log($e);
+			UI::alert('warning', $e->getMessage());
+		} catch (AlertInfoException $e) {
+			DBG::log($e);
+			UI::alert('info', $e->getMessage());
 		} catch (Exception $e) {
 			DBG::log($e);
 			UI::alert('danger', $e->getMessage());

+ 2 - 2
SE/se-lib/tmpl/_layout_gora.php

@@ -23,8 +23,8 @@
 	<script src="static/bootstrap/js/bootstrap.min.js"></script>
 	<script src="stuff/jquery.selectize/js/standalone/selectize.min.js"></script>
 	<script src="stuff/numeral/numeral.min.js"></script>
-	<script src="stuff/moment/moment.min.js"></script>
-	<script src="stuff/moment/pl.js"></script>
+	<script src="static/moment/moment.min.js"></script>
+	<script src="static/moment/pl.js"></script>
 	<script src="static/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
 	<script src="stuff/notify.min.js"></script>
 	<script src="stuff/jquery-ui-1.10.4.custom.min.js"></script>

File diff suppressed because it is too large
+ 5 - 0
SE/static/moment/moment.min.js


+ 100 - 0
SE/static/moment/pl.js

@@ -0,0 +1,100 @@
+// moment.js locale configuration
+// locale : polish (pl)
+// author : Rafal Hirsz : https://github.com/evoL
+
+(function (factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(['moment'], factory); // AMD
+    } else if (typeof exports === 'object') {
+        module.exports = factory(require('../moment')); // Node
+    } else {
+        factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
+    }
+}(function (moment) {
+    var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'),
+        monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
+
+    function plural(n) {
+        return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
+    }
+
+    function translate(number, withoutSuffix, key) {
+        var result = number + ' ';
+        switch (key) {
+        case 'm':
+            return withoutSuffix ? 'minuta' : 'minutę';
+        case 'mm':
+            return result + (plural(number) ? 'minuty' : 'minut');
+        case 'h':
+            return withoutSuffix  ? 'godzina'  : 'godzinę';
+        case 'hh':
+            return result + (plural(number) ? 'godziny' : 'godzin');
+        case 'MM':
+            return result + (plural(number) ? 'miesiące' : 'miesięcy');
+        case 'yy':
+            return result + (plural(number) ? 'lata' : 'lat');
+        }
+    }
+
+    return moment.defineLocale('pl', {
+        months : function (momentToFormat, format) {
+            if (/D MMMM/.test(format)) {
+                return monthsSubjective[momentToFormat.month()];
+            } else {
+                return monthsNominative[momentToFormat.month()];
+            }
+        },
+        monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
+        weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
+        weekdaysShort : 'nie_pon_wt_śr_czw_pt_sb'.split('_'),
+        weekdaysMin : 'N_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
+        longDateFormat : {
+            LT : 'HH:mm',
+            LTS : 'LT:ss',
+            L : 'DD.MM.YYYY',
+            LL : 'D MMMM YYYY',
+            LLL : 'D MMMM YYYY LT',
+            LLLL : 'dddd, D MMMM YYYY LT'
+        },
+        calendar : {
+            sameDay: '[Dziś o] LT',
+            nextDay: '[Jutro o] LT',
+            nextWeek: '[W] dddd [o] LT',
+            lastDay: '[Wczoraj o] LT',
+            lastWeek: function () {
+                switch (this.day()) {
+                case 0:
+                    return '[W zeszłą niedzielę o] LT';
+                case 3:
+                    return '[W zeszłą środę o] LT';
+                case 6:
+                    return '[W zeszłą sobotę o] LT';
+                default:
+                    return '[W zeszły] dddd [o] LT';
+                }
+            },
+            sameElse: 'L'
+        },
+        relativeTime : {
+            future : 'za %s',
+            past : '%s temu',
+            s : 'kilka sekund',
+            m : translate,
+            mm : translate,
+            h : translate,
+            hh : translate,
+            d : '1 dzień',
+            dd : '%d dni',
+            M : 'miesiąc',
+            MM : translate,
+            y : 'rok',
+            yy : translate
+        },
+        ordinalParse: /\d{1,2}\./,
+        ordinal : '%d.',
+        week : {
+            dow : 1, // Monday is the first day of the week.
+            doy : 4  // The week that contains Jan 4th is the first week of the year.
+        }
+    });
+}));

+ 7 - 7
SE/static/p5UI/initP5MainMenuDropdown.js

@@ -78,14 +78,14 @@ function initP5MainMenuDropdown( btnNode, idSubMenu ) {
 		var jqDropdownTrigger = jQuery(btnNode)
 		var jqDropdownMenu = jQuery('#' + idSubMenu)
 		var jqDropdownParent = jqDropdownMenu.parent()
+		var rerenderDropdown = (function (global, idSubMenu) {
+			return function (data) {
+				console.log('DBG renderP5MainMenuDropdown', {data, idSubMenu});
+				renderP5MainMenuDropdown(data, idSubMenu);
+			}
+		})(global, idSubMenu);
 
-		global.p5UI__MenuStore.subscribe(
-			(function (global, idSubMenu) {
-				return function (data) {
-					renderP5MainMenuDropdown(data, idSubMenu);
-				}
-			})(global, idSubMenu)
-		)
+		global.p5UI__MenuStore.subscribe(rerenderDropdown)
 
 		jqDropdownTrigger.attr('data-toggle', 'dropdown') // is required by bootstrap dorpdown.js evenf if is called via js
 

Some files were not shown because too many files changed in this diff