<?php
/**
 * by plabudda 2013-10-09:
 *
 * 1. install:
 *   - create *.plist file:
 *     # php /Library/Server/Web/Data/Sites/Default/SE/se-dev-pl/bash_sync_perms.php biuro.biall-net.pl addToLaunchctl
 *     # php /Library/Server/Web/Data/Sites/Default/SE/bash_sync_perms.php biuro.medical-opakowania.pl addToLaunchctl
 *   - load by launchctl:
 *     # launchctl load /Library/LaunchDaemons/pl.biallnet.sync_perms.php.plist
 *
 * 2. uninstall:
 *   - unload from launchctl:
 *     # launchctl unload /Library/LaunchDaemons/pl.biallnet.sync_perms.php.plist
 *
 * 3. debug:
 *   - debug by www:
 *     /SE/se-dev-pl/bash_sync_perms.php?DBG=1
 *   - more verbose debug:
 *     /SE/se-dev-pl/bash_sync_perms.php?DBG=6
 *   - task:
 *     /SE/se-dev-pl/bash_sync_perms.php?task=task
 *     /SE/se-dev-pl/bash_sync_perms.php?task=newFiles
 *     /SE/se-dev-pl/bash_sync_perms.php?task=testParse
 * 		 /SE/se-dev-pl/bash_sync_perms.php?task=task&PROJ_ID={projID}  -- force update project
 *
 *
 * 4. Sync users: `ADMIN_USERS`.`A_STATUS` - OFF - manual sync from users table (groups from CRM_LISTA_ZASOBOW)
 *   'WAITING' - Kandydaci
 *   'NORMAL' - Aktywni pracownicy
 *   'DELETED' - Wyłączone konta
 *   'OFF_SOFT' - ?
 *   'OFF_HARD' - ?
 *
 * @2014-01 recomended by A.Binder migration to separate dirs of koresp scripts:
 * robienie katalogow dla korespondencji przy migracji na nowy typ pism
  #read PASSWORD
  #for a in `echo "select concat(ID,';---;',K_OD_KOGO,K_ZAWARTOS) from IN7_DZIENNIK_KORESP" |mysql -p$PASSWORD -uroot -Damssystem -h127.0.0.1  | sed 's/[^a-zA-Z0-9;-]/_/g' |sed 's/;---;/\./g' ` ;
  #do
  #echo mkdir $a
  #mkdir $a
  #done
 *do wywolania w katalogach IN/OUT:
 # cd IN
 # for a in `ls |awk -F'.' '{ print $1 }' ` ; do mv $a.* ../$a.*; done
 *
 *
 *
 * DONE: koresp format: PISMA/ID/...
 * DONE: projekty -> koresp hardlink PROJEKTY/ID..../_PISMA/{HL:pismo.pdf}
 * TODO: PROJEKTY/ID..../_PISMA/ permy tylko read/listing
 * TODO: hardlink druki: mkdir DRUKI/ID.{DESC}/_PROJEKT/{proj_folder}/{HL:pliki z proj bez podkatalogów}
 * TODO: DRUKI/ID.{DESC}/_PROJEKT/{proj_folder} permy tylko read/listing
 * -- TODO: DRUKI/ID.{DESC}/ - permy z bazy danych A_ADM_COMPANY, A_CLASSIFIED
 *
 *
 * DONE: Trigger po update/insert Koresp po aktualizacji permów plików projektu
 *
 * 5. Sync custom tables
 *     TEST 2014-10-27: # php /Users/plabudda/rsync-dev/SE/bash_sync_perms.php biuro.biall-net.pl SyncPermsCustomTables
 * 5.1 Table list in CRM_CONFIG(key=_SYNC_TABLE_FILE_PERMS__TablesList)
 *     table must be defined in .cnf--folders-{domain}.ini.php as section [{tblName}_COLUMN]
 *     table must have fields: ID, L_APPOITMENT_USER, A_ADM_COMPANY, A_CLASSIFIED @see generateTasksNewRecords
 */

ini_set('max_execution_time', 300);
ini_set('memory_limit', '512M');

define('DS', DIRECTORY_SEPARATOR);
define('APP_PATH_ROOT', dirname(__FILE__));
define('APP_PATH_WWW', dirname(__FILE__));
define('APP_PATH_CONFIG', APP_PATH_ROOT . DS . 'config');

// set ini to log errors into error log file
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/apache2/error_log');// TODO: php.ini ?
date_default_timezone_set('Europe/Warsaw');// PHP 5 >= 5.1.0 required by date functions

require_once APP_PATH_ROOT . DS . 'se-lib' . DS . 'Lib.php';
Lib::loadClass('V');
Lib::loadClass('Config');
Lib::loadClass('DB');
Lib::loadClass('User');
Lib::loadClass('FoldersConfig');
Lib::loadClass('FileUploader');

if (isset($_SERVER["argv"][1])) {
	$_SERVER['SERVER_NAME'] = $_SERVER["argv"][1];
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'info') {
	$info = array();
	$info['max_execution_time'] = ini_get('max_execution_time');
	$info['memory_limit'] = ini_get('memory_limit');
	echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">info (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($info);echo'</pre>';

	ini_set('max_execution_time', 300);
	ini_set('memory_limit', '512M');
	$info = array();
	$info['max_execution_time'] = ini_get('max_execution_time');
	$info['memory_limit'] = ini_get('memory_limit');
	echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">info (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($info);echo'</pre>';

	// $requirePermsExt = SyncPerms::getRequiredPermsExtByRecord((object)[
	// 	'L_APPOITMENT_USER' => "plabudda",
	// 	'A_ADM_COMPANY' => "",
	// 	'A_CLASSIFIED' => "",
	// 	'PARENT_L_APPOITMENT_USER' => "a.binder",
	// ]);
	// DBG::nicePrint($requirePermsExt, '$requirePermsExt - public for workgroup');
	// $requirePermsExt = SyncPerms::getRequiredPermsExtByRecord((object)[
	// 	'L_APPOITMENT_USER' => "plabudda",
	// 	'A_ADM_COMPANY' => "2948-programista",
	// 	'A_CLASSIFIED' => "2948-programista",
	// 	'PARENT_L_APPOITMENT_USER' => "a.binder",
	// ]);
	// DBG::nicePrint($requirePermsExt, '$requirePermsExt - only 2948-programista read,write');
	// $requirePermsExt = SyncPerms::getRequiredPermsExtByRecord((object)[
	// 	'L_APPOITMENT_USER' => "plabudda",
	// 	'A_ADM_COMPANY' => "",
	// 	'A_CLASSIFIED' => "2948-programista",
	// 	'PARENT_L_APPOITMENT_USER' => "a.binder",
	// ]);
	// DBG::nicePrint($requirePermsExt, '$requirePermsExt - 2948-programista read');
	// $requirePermsExt = SyncPerms::getRequiredPermsExtByRecord((object)[
	// 	'L_APPOITMENT_USER' => "plabudda",
	// 	'A_ADM_COMPANY' => "2948-programista",
	// 	'A_CLASSIFIED' => "",
	// 	'PARENT_L_APPOITMENT_USER' => "a.binder",
	// ]);
	// DBG::nicePrint($requirePermsExt, '$requirePermsExt - 2948-programista write');
	//
	// $vFile = (object)[
	// 	'path' => 'test-file-1.txt',
	// 	'isDir' => false,
	// 	'permsExt' => [],
	// ];
	// $permReadDir = "list,search,readattr,readextattr,readsecurity,file_inherit,directory_inherit";
	// $permWriteDir = "add_file,add_subdirectory,delete_child,writeattr,writeextattr,writesecurity,chown";
	// $permReadFile = "read,execute,readattr,readextattr,readsecurity";
	// $permWriteFile = "write,append,writeattr,writeextattr,writesecurity,chown";
	//
	// $recordPath = "/Library/Server/Web/Data/Sites/Default/PLIKI/TEST_PERMS/90";
	// $filePath = "{$recordPath}/{$vFile->path}";
	//
	// $cmdList = SyncPerms::fixFilePerms($vFile, $requirePermsExt, $recordPath);
	// DBG::nicePrint($cmdList, '$cmdList - 2948-programista write');
	//
	// V::exec("ls -le '{$filePath}'", $out, $ret);
	// DBG::nicePrint($out, "\$out - ret({$ret})");
	//
	// V::exec("echo \"" . implode("\n", [
	// 	"user:_www allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// 	"group:27_BIALL-NET allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// ]) . "\" | chmod -E '{$filePath}'", $out, $ret);
	// DBG::nicePrint($out, "\$out - ret({$ret})");
	//
	// V::exec("ls -le '{$filePath}'", $out, $ret);
	// DBG::nicePrint($out, "\$out - test 1, ret({$ret})");
	//
	// $aclPermsOut = implode("\n", [
	// 	"user:_www allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// 	"group:27_BIALL-NET allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// ]);
	// $aclPermsOut = implode("\n", [
	// 	"user:_www allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// 	"group:27_BIALL-NET allow read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown",
	// 	"user:plabudda allow read,write",
	// ]);
	// V::exec("cd '{$recordPath}'; find . -type f | while read -r file; do echo \"fix file perms: \$file\"; echo \"{$aclPermsOut}\" | chmod -E \"\${file}\"; done", $out, $ret);
	// DBG::nicePrint($out, "\$out - ret({$ret})");
	//
	// V::exec("ls -le '{$filePath}'", $out, $ret);
	// DBG::nicePrint($out, "\$out - test 2, ret({$ret})");
	//
	// $testPermsFile = "{$recordPath}/perms-90.txt";
	// DBG::nicePrint(explode("\n", file_get_contents($testPermsFile)), "testPermsFile - before add line");
	// $lineToAdd = "user:plabudda allow read,write";
	// V::exec("cat '{$testPermsFile}' | grep '{$lineToAdd}' || echo \"{$lineToAdd}\" >> '{$testPermsFile}'", $out, $ret);
	// DBG::nicePrint($out, "\$out - add line, ret({$ret})");
	// DBG::nicePrint(explode("\n", file_get_contents($testPermsFile)), "testPermsFile - after add line");
	//
	// { // TODO: add to instalator / install check / ?Status
	// 	$lineToAdd = "_www ALL = NOPASWD: '" . APP_PATH_ROOT . "/SE/index-cli.php'";
	// 	$destFile = "/etc/sudoers";
	// 	V::exec("cat '{$destFile}' | grep '{$lineToAdd}' || echo \"{$lineToAdd}\" >> '{$destFile}'", $out, $ret);
	// 	// TODO: check if sudo index-cli.php can exec("sudo ...")
	// }
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'users') {
	SyncPerms::run(null, null, 'users', null);
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'usersAll') {
	SyncPerms::run(null, null, 'usersAll', null);
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'task') {
	SyncPerms::run(null, null, 'nextTask', null);
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'newFiles') {
	SyncPerms::run(null, null, 'newFiles', null);
}
else if (empty($_SERVER["argv"]) && V::get('task', '', $_GET) == 'testParse') {
	$cmd = " ls -Rlea . ";
	exec("cd '/Library/Server/Web/Data/Sites/Default/PLIKI/PROJEKTY/1657.Rekrutacja_z_dnia_2012-06_25_na_stanowisko_Przedstawiciel_Handlowy/1/'; {$cmd} ", $filesWithPerms);
	//$line = "-rwxrwx---+ 1 _www  workgroup  212367 Feb 25  2013 {file_name}";
echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$filesWithPerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($filesWithPerms);echo'</pre>';
	$filesWithPermsTree = array();

	$lastName = '';
	$folderParent = '';
	foreach ($filesWithPerms as $line) {
		if (substr($line, 0, 1) == '-' || substr($line, 0, 1) == 'd') {// file name
			$fileParts = array();
			$filePartsArr = explode(' ', $line);
			$lastInd = 0;
			foreach ($filePartsArr as $vPart) {
				if ($lastInd > 7) {// file name
					$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
					$lastInd++;
				}
				else if (!empty($vPart) || $vPart === '0') {
					$fileParts[] = $vPart;
					$lastInd++;
				}
			}

			if (end($fileParts) == '..' || (end($fileParts) == '.' && $folderParent)) {
				$lastName = '..';
				continue;
			}

			$file = new stdClass();

			if (!isset($fileParts[8])) {
				echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
				//continue;
			} else {
				// fix file name from bad chars
				//   [8] => CV_Kowalski Mariusz`.pdf
				//    "-$Model Bialnet .xlsm"  in /Users/plabudda/se-dev-pl/SE/bash_sync_perms.php on line 1518
				//   [11-Oct-2013 21:00:28 UTC] PHP Notice:  Error-cmd: chmod -a# 3 "/Library/Server/Web/Data/Sites/Default/PLIKI/PROJEKTY/500.2009-12-01.INNE.DOFINANSOWANIE_PROJEKT_KOLBUDY/2013-02-26_harmonogram_EDORADCA_Aktualny/-$harmonogram 01.2013.xlsx"  in /Users/plabudda/se-dev-pl/SE/bash_sync_perms.php on line 1518
				//   [11-Oct-2013 21:00:27 UTC] PHP Notice:  Error-cmd: chmod -a# 3 "/Library/Server/Web/Data/Sites/Default/PLIKI/PROJEKTY/500.2009-12-01.INNE.DOFINANSOWANIE_PROJEKT_KOLBUDY/2012-04-19_oferta_gpon_sprint_po_poprawkach/-$Wycena GPON 2012 Biall-Net_spr v1.xlsx"  in /Users/plabudda/se-dev-pl/SE/bash_sync_perms.php on line 1518
				//   [11-Oct-2013 20:56:25 UTC] PHP Notice:  Error-cmd: chmod +a "user:prejsi allow write,append,writeattr,writeextattr,writesecurity,chown,read,execute,readattr,readextattr,readsecurity" "/Library/Server/Web/Data/Sites/Default/PLIKI/PROJEKTY/1695.Projekt_glowny_dzialu_rozwoju_biznesu_oraz_dystrybucji_informacji/HANDLOWY/HANDLOWY_2012/OTOMIN/-$Otomin.xlsx"  in /Users/plabudda/se-dev-pl/SE/bash_sync_perms.php on line 1517

				//$fileParts[8] = str_replace('\\', '\\\\', $fileParts[8]);
				//$fileParts[8] = str_replace(array('`', '$'), array('\`', '\$'), $fileParts[8]);
			}
echo'<pre style="max-height:230px;overflow:auto;border:1px solid red;text-align:left;">line/$fileParts/$filePartsArr (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($line, $fileParts, $filePartsArr));echo'</pre>';

			$file->name = end($fileParts);
			$file->path = array();
			if ($folderParent) $file->path[] = $folderParent;
			$file->path[] = end($fileParts);
			$file->path = implode('/', $file->path);
			//$file->owner = $fileParts[2];
			//$file->group = $fileParts[3];
			//$file->perms = $fileParts[0];
			$file->isDir = (substr($line, 0, 1) == 'd');
			$file->permsExt = array();
			$filesWithPermsTree[$file->name] = $file;
			$lastName = $file;
		}
		else if (substr($line, 0, 1) == ' ') {// extended perm line
			if ($lastName == '..') {
				continue;
			}
			$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
		}
		else if (substr($line, 0, 2) == './' && substr($line, -1) == ':') {// folder
			$folderParent = substr($line, 2, -1);
		}
		else if (substr($line, 0, 5) == 'total') {
			continue;
		}
		else if (empty($line)) {
			$folderParent = '';
			continue;
		}
	}
	echo'<pre style="max-height:400px;overflow:auto;border:1px solid red;text-align:left;">$filesWithPermsTree (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($filesWithPermsTree);echo'</pre>';
}
else if (empty($_SERVER["argv"]) && V::get('DBG', '', $_GET, 'int') > 0) {
	SyncPerms::run(null, null);
}
else {
	die("\n The second argument should be server domain - e.g. biuro.kike.pl \n");
}

if (!empty($_SERVER["argv"][0]) && !empty($_SERVER["argv"][1])) {
	$arg2 = (!empty($_SERVER["argv"][2]))? $_SERVER["argv"][2] : null;
	$arg3 = (!empty($_SERVER["argv"][3]))? $_SERVER["argv"][3] : null;

	SyncPerms::run($_SERVER["argv"][0], $_SERVER["argv"][1], $arg2, $arg3);
}


class SyncPerms {

	public static function run($script_name, $domain, $arg2 = null, $arg3 = null) {
		if ($arg2 == 'addToLaunchctl') self::task__addToLaunchctl($domain);
		else if ($arg2 == 'newFiles') self::task__newFiles();
		else if ($arg2 == 'nextTask') self::task__nextTask();
		else if ($arg2 == 'testErrorLog') self::task__testErrorLog();
		else if ($arg2 == 'users') self::usersAction();
		else if ($arg2 == 'usersAll') self::usersAllAction();
		else if ($arg2 == 'SyncPermsCustomTables') self::task__SyncPermsCustomTables();
		else if ($arg2 == 'sendNotify') self::sendNotify();
		else self::task__default();
	}

	public static function task__addToLaunchctl($domain) {
		$plistFile = "/Library/LaunchDaemons/pl.biallnet.sync_perms.{$domain}.php.plist";
		system("echo \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Disabled</key>
	<false/>
	<key>Label</key>
	<string>pl.biallnet.sync_perms.{$domain}.php</string>
	<key>UserName</key>
	<string>root</string>
	<key>ProgramArguments</key>
	<array>
		 <string>php</string>
			<string>{$_SERVER["argv"][0]}</string>
			<string>{$_SERVER["argv"][1]}</string>
	</array>
	<key>StartInterval</key>
	<integer>60</integer>
</dict>
</plist>

\" > {$plistFile}");
		echo "Run as root: launchctl load {$plistFile} \n";
	}

	public static function task__newFiles() {
		$time = new stdClass();
		$time->start = time();
		self::newFilesAction();
		$time->end = time();
		// TODO: print operation time if DBG
	}

	public static function task__nextTask() {
		$time = new stdClass();
		$time->start = time();

		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:2px solid red;text-align:left;">nextTask</pre>';}
		self::installTable();
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:2px solid red;text-align:left;">installTable DONE</pre>';}
		self::generateTasksNewRecords();
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:2px solid red;text-align:left;">generateTasksNewRecords DONE</pre>';}
		self::generateTasksUpdated();
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:2px solid red;text-align:left;">generateTasksUpdated DONE</pre>';}

		$limit = V::get('_limit', 1, $_GET, 'int');
		for ($i = 0; $i < $limit; $i++) {
			$time->cur = time();
			if ($time->cur - $time->start > 60 * 5) {
				if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:2px solid red;text-align:left;">Force-break time('.($time->cur - $time->start).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';echo'</pre>';}
				break;
			}

			$task = self::nextTaskAction();
			if (!$task) break;
		}
	}

	public static function task__testErrorLog() {
		echo "display_errors: " . ini_get("display_errors") . "\n";
		echo "display_startup_errors: " . ini_get("display_startup_errors") . "\n";
		echo "log_errors: " . ini_get("log_errors") . "\n";
		echo "error_reporting: " . ini_get("error_reporting") . "\n";
		echo "error_log: " . ini_get("error_log") . "\n";
		trigger_error("Notice: Test Error Log", E_USER_NOTICE);
	}

	public static function task__SyncPermsCustomTables() {
		$DBG_TIME = true;
		$dbgTime = new stdClass();
		$dbgTime->startTime = microtime(true);
		$dbgTime->lastTime = $dbgTime->startTime;

		$customSync = new SyncPermsCustomTables();
		// TODO: $customSync->run();
		$customSync->fetchConfig();
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-fetchConfig (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}
		$customSync->installTable();
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-installTable (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		$rowId = (int)$arg3;
		if ($rowId > 0) {
			$customSync->executeTask('DEALS_TABLE', $rowId);// TODO: DEALS_TABLE ?
		}
		else {
			$customSync->generateTasksNewRecords();
			if ($DBG_TIME) {
				$dbgTime->curTime = microtime(true);
				trigger_error("DBG:time after-generateTasksNewRecords (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
				$dbgTime->lastTime = $dbgTime->curTime;
			}
			$customSync->generateTasksUpdated();
			if ($DBG_TIME) {
				$dbgTime->curTime = microtime(true);
				trigger_error("DBG:time after-generateTasksUpdated (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
				$dbgTime->lastTime = $dbgTime->curTime;
			}
			$customSync->newFilesAction();
			if ($DBG_TIME) {
				$dbgTime->curTime = microtime(true);
				trigger_error("DBG:time after-newFilesAction (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
				$dbgTime->lastTime = $dbgTime->curTime;
			}
			$limit = 1000;
			for ($i = 0; $i < $limit; $i++) {
				$loopTasksFired = 0;
				foreach ($customSync->_tbls as $tblName => $tblConf) {
					$task = $customSync->nextTaskAction($tblName);
					if ($DBG_TIME) {
						$dbgTime->curTime = microtime(true);
						trigger_error("DBG:time after-nextTaskAction (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
						$dbgTime->lastTime = $dbgTime->curTime;
					}
					if (!$task) {
						// TODO: rm $tblName from config to prevent searching for task in next loops
						unset($customSync->_tbls[$tblName]);
						continue;
					}
					$loopTasksFired++;
				}
				if (!$loopTasksFired) {
					if ($DBG_TIME) {
						$dbgTime->curTime = microtime(true);
						trigger_error("DBG:time after-nextTaskAction (loop:{$i}) (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
						$dbgTime->lastTime = $dbgTime->curTime;
					}
					break;
				}
				$i += $loopTasksFired;
			}
		}

		$dbgTime->endTime = microtime(true);
		trigger_error("Notice: sync end OK (time:" . number_format($dbgTime->endTime - $dbgTime->startTime, 6) . "s) !", E_USER_NOTICE);
	}

	public static function task__default() {
		$DBG_TIME = false;
		$DBG_MEMORY = false;
		$dbgTime = new stdClass();
		$dbgTime->start = time();
		$dbgTime->startTime = microtime(true);
		$dbgTime->lastTime = $dbgTime->startTime;

		self::installTable();

		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-installTable (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		self::generateTasksNewRecords();

		if($DBG_MEMORY)trigger_error("DBG:mem usage after-generateTasksNewRecords " . memory_get_usage(true) . ".", E_USER_NOTICE);
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-generateTasksNewRecords (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		self::generateTasksUpdated();

		if($DBG_MEMORY)trigger_error("DBG:mem usage after-generateTasksUpdated " . memory_get_usage(true) . ".", E_USER_NOTICE);
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-generateTasksUpdated (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		$dbgTime->cur = time();
		if ($dbgTime->cur - $dbgTime->start > 60 * 5) {
			trigger_error("Error: time end after-init " . ($dbgTime->cur - $dbgTime->start) . " !", E_USER_NOTICE);
			return;
		}

		self::newFilesAction();

		if($DBG_MEMORY)trigger_error("DBG:mem usage after-newFilesAction " . memory_get_usage(true) . ".", E_USER_NOTICE);
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time after-newFilesAction (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		$limit = 100;
		for ($i = 0; $i < $limit; $i++) {
			$dbgTime->cur = time();
			if ($dbgTime->cur - $dbgTime->start > 60 * 5) {
				trigger_error("Error: time end after-nextTaskAction(loop:{$i}) " . ($dbgTime->cur - $dbgTime->start) . " !", E_USER_NOTICE);
				return;
			}

			$task = self::nextTaskAction();
			if($DBG_MEMORY)trigger_error("DBG:mem usage after-nextTaskAction (loop:{$i}) " . memory_get_usage(true) . ".", E_USER_NOTICE);
			if ($DBG_TIME) {
				$dbgTime->curTime = microtime(true);
				trigger_error("DBG:time after-nextTaskAction (loop:{$i}) (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
				$dbgTime->lastTime = $dbgTime->curTime;
			}

			if (!$task) {
				$dbgTime->cur = time();
				break;
			}
		}

		$customSync = new SyncPermsCustomTables();
		$customSync->run();

		$dbgTime->cur = time();
		$dbgTime->endTime = microtime(true);
		trigger_error("Notice: sync end OK (time:" . number_format($dbgTime->endTime - $dbgTime->startTime, 6) . "s) !", E_USER_NOTICE);
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			trigger_error("DBG:time sync end OK (time:" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s)", E_USER_NOTICE);
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		self::sendNotify();

		Lib::loadClass('Router');
		echo "DBG: UrlAction_WmsGenerate->doGenerate() START...\n";
		Router::getRoute('UrlAction_WmsGenerate')->doGenerate();
		echo "DBG: UrlAction_WmsGenerate->doGenerate() END\n";

		echo "DBG: UrlAction_BiAuditGenerate->doGenerate() START...\n";
		Router::getRoute('UrlAction_BiAuditGenerate')->doGenerate(); //musze przetestowac raport!
		echo "DBG: UrlAction_BiAuditGenerate->doGenerate() END\n";
	}

	public static function sendNotify() {
		Lib::loadClass('Router');
		$cronTaskName = 'sendNotify';
		$keyToken = 'bach_sync_perms';
		$token = Router::getRoute('Cron')->generateCliAuthToken($keyToken, $cronTaskName, 300 * 10);
		echo "DBG: token = '{$token}'" . "\n";
		$string = Router::getRoute('Cron')->executeCurlTastByToken($cronTaskName, $keyToken, $token);
		echo "DBG: string -----------------------\n{$string}\nEOF string---------------------------------\n";
	}

	public static function installTable() {
		$sql = "
			CREATE TABLE IF NOT EXISTS `_SYNC_FILE_PERMS` (
				`ID` int(11) NOT NULL AUTO_INCREMENT,
				`ID_PROJECT` int(11) NOT NULL,
				`L_APPOITMENT_USER` varchar(64) DEFAULT '',
				`A_ADM_COMPANY` varchar(64) NOT NULL DEFAULT '',
				`A_CLASSIFIED` varchar(64) NOT NULL DEFAULT '',
				`PARENT_L_APPOITMENT_USER` varchar(64) NOT NULL DEFAULT '',
				`A_LAST_SYNC` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
				`A_SYNCHRONIZED` tinyint(4) NOT NULL DEFAULT '0',
				`A_SYNC_RESULT` varchar(255) NOT NULL DEFAULT '',
				PRIMARY KEY (`ID`),
				UNIQUE KEY `ID_PROJECT` (`ID_PROJECT`)
			) ENGINE=MyISAM  DEFAULT CHARSET=latin2 ;
		";
		DB::getPDO()->execSql($sql);

		try {
			$sql = " DROP TRIGGER IF EXISTS `KORESP_change_ID_PROJECT`; ";
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			// TODO: SKIP - BUG after upgrade mysql - @see https://dev.mysql.com/doc/refman/5.5/en/upgrading-from-previous-series.html
		}

		try {
			$sql = "
				CREATE DEFINER=`root`@`localhost` TRIGGER `KORESP_change_ID_PROJECT` AFTER UPDATE ON `IN7_DZIENNIK_KORESP`
				 FOR EACH ROW BEGIN
						IF (NEW.`ID_PROJECT` != OLD.`ID_PROJECT` and NEW.`ID_PROJECT` > 0) or OLD.`M_DIST_FILES`!=NEW.`M_DIST_FILES` THEN
							update `_SYNC_FILE_PERMS` as s set
								s.`A_SYNCHRONIZED`=0
							where
								s.`ID_PROJECT`=NEW.`ID_PROJECT`
								or s.`ID_PROJECT`=OLD.`ID_PROJECT`;
						END IF;
				END
			";
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}

		try {
			$sql = " DROP TRIGGER IF EXISTS `KORESP_add_ID_PROJECT`; ";
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			// TODO: SKIP - BUG after upgrade mysql - @see https://dev.mysql.com/doc/refman/5.5/en/upgrading-from-previous-series.html
		}

		try {
			$sql = "
				CREATE DEFINER=`root`@`localhost` TRIGGER `KORESP_add_ID_PROJECT` AFTER INSERT ON `IN7_DZIENNIK_KORESP`
				 FOR EACH ROW BEGIN
						IF NEW.`ID_PROJECT` > 0 THEN
							update `_SYNC_FILE_PERMS` as s set
								s.`A_SYNCHRONIZED`=0
							where
								s.`ID_PROJECT`=NEW.`ID_PROJECT`;
						END IF;
				END
			";
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}
	}

	public static function newFilesAction() {
		$task = self::getTaskNewFiles();
		$taskResult = self::fixLastModifiedFiles($task);
		self::saveTask(-1);
	}

	public static function nextTaskAction() {
		$task = self::getTask();
		if (!$task) return false;
		self::executeTask($task);

		/*
		if (isset($task->A_SYNC_RESULT->total)
			&& isset($task->A_SYNC_RESULT->lastDone)
			&& $task->A_SYNC_RESULT->lastDone < $task->A_SYNC_RESULT->total
		) {
			self::saveTask($task->ID_PROJECT, 0, $task->A_SYNC_RESULT);
		} else {
			self::saveTask($task->ID_PROJECT);
		}
		*/
		self::saveTask($task->ID_PROJECT);
		return $task;
	}

	public static function generateTasksUpdated() {
		$sqlTest = "
			select p.ID, p.`L_APPOITMENT_USER`, p.`A_ADM_COMPANY`, p.`A_CLASSIFIED`, s. *
			from `_SYNC_FILE_PERMS` as s, `IN7_MK_BAZA_DYSTRYBUCJI` as p, `IN7_MK_BAZA_DYSTRYBUCJI` as pp
			where
				s.`ID_PROJECT`=p.`ID` and pp.`ID`=p.`P_ID`
				and (
					s.`L_APPOITMENT_USER`!=p.`L_APPOITMENT_USER`
					or s.`A_ADM_COMPANY`!=p.`A_ADM_COMPANY`
					or s.`A_CLASSIFIED`!=p.`A_CLASSIFIED`
					or s.`PARENT_L_APPOITMENT_USER`!=pp.`L_APPOITMENT_USER`
				)
		";

		$sql = "update `_SYNC_FILE_PERMS` as s, `IN7_MK_BAZA_DYSTRYBUCJI` as p, `IN7_MK_BAZA_DYSTRYBUCJI` as pp
			set
				s.`L_APPOITMENT_USER`=p.`L_APPOITMENT_USER`
				, s.`A_ADM_COMPANY`=p.`A_ADM_COMPANY`
				, s.`A_CLASSIFIED`=p.`A_CLASSIFIED`
				, s.`PARENT_L_APPOITMENT_USER`=pp.`L_APPOITMENT_USER`
				, s.`A_SYNCHRONIZED`=0
			where
				s.`ID_PROJECT`=p.`ID` and pp.`ID`=p.`P_ID`
				and (
					s.`L_APPOITMENT_USER`!=p.`L_APPOITMENT_USER`
					or s.`A_ADM_COMPANY`!=p.`A_ADM_COMPANY`
					or s.`A_CLASSIFIED`!=p.`A_CLASSIFIED`
					or s.`PARENT_L_APPOITMENT_USER`!=pp.`L_APPOITMENT_USER`
				)
		";
		try {
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}
	}

	public static function generateTasksNewRecords() {
		$sql = "insert ignore into `_SYNC_FILE_PERMS` (`ID_PROJECT`, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`, `PARENT_L_APPOITMENT_USER`)
			values (-1, '', '', '', '');
		";
		try {
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}

		$sql = "insert ignore into `_SYNC_FILE_PERMS` (`ID_PROJECT`, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`, `PARENT_L_APPOITMENT_USER`)
			select p.`ID`, p.`L_APPOITMENT_USER`, p.`A_ADM_COMPANY`, p.`A_CLASSIFIED`, pp.`L_APPOITMENT_USER`
			from `IN7_MK_BAZA_DYSTRYBUCJI` as p
				left join `IN7_MK_BAZA_DYSTRYBUCJI` as pp on (pp.`ID`=p.`P_ID`)
			where p.`A_STATUS`!='DELETED'
		";
		try {
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}
	}

	/**
	 * Znajdź najstarszy aktualizowany projekt.
	 */
	public static function getTask() {
		$sqlWhereProjID = " s.`A_SYNCHRONIZED`=0 ";
		if (($projID = V::get('PROJ_ID', '', $_GET, 'int')) > 0) {
			$sqlWhereProjID = " s.`ID_PROJECT`='{$projID}' ";
		}
		$sql = "
			select s.*
				, p.`L_APPOITMENT_USER` as p__L_APPOITMENT_USER
				, p.`A_ADM_COMPANY` as p__A_ADM_COMPANY
				, p.`A_CLASSIFIED` as p__A_CLASSIFIED
				, pp.`L_APPOITMENT_USER` as p__PARENT_L_APPOITMENT_USER
				, p.`M_DIST_DESC` as p__M_DIST_DESC
				, p.`M_DISTRIBUTOR` as p__M_DISTRIBUTOR
				, p.`CRM_LISTA_ZASOBOW_ID` as p__CRM_LISTA_ZASOBOW_ID
			from `_SYNC_FILE_PERMS` as s
				left join `IN7_MK_BAZA_DYSTRYBUCJI` as p on(p.`ID`=s.`ID_PROJECT`)
				left join `IN7_MK_BAZA_DYSTRYBUCJI` as pp on(pp.`ID`=p.`P_ID`)
			where {$sqlWhereProjID}
			order by s.`A_LAST_SYNC` asc, s.`ID` asc
			limit 1
		";
		try {
			$task = DB::getPDO()->fetchFirst($sql);
			if ($task) {
				$task['A_SYNC_RESULT'] = (!$task['A_SYNC_RESULT'])? new stdClass() : json_decode($task['A_SYNC_RESULT']);
				return (object)$task;
			}
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}
		return null;
	}

	public static function getTaskNewerUsers() {
		$sql = "
			select s.*
			from `_SYNC_FILE_PERMS` as s
			where s.`ID_PROJECT`=-2
		";
		try {
			$task = DB::getPDO()->fetchFirst($sql);
			if ($task) {
				return (object)$task;
			}
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}

		return (object)[
			'ID_PROJECT' => -2,
			'A_SYNC_RESULT' => new stdClass(),
			'A_LAST_SYNC' => '', // date("Y-m-d H:i", mktime(date("H"), date("i") - 10, date("s"), date("n"), date("j"), date("Y"))),
		];
	}

	public static function getTaskNewFiles() {
		$sql = "
			select s.*
			from `_SYNC_FILE_PERMS` as s
			where s.`ID_PROJECT`=-1
		";
		try {
			$task = DB::getPDO()->fetchFirst($sql);
			if ($task) {
				return (object)$task;
			}
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}

		return (object)[
			'ID_PROJECT' => -1,
			'A_SYNC_RESULT' => new stdClass(),
			'A_LAST_SYNC' => date("Y-m-d H:i", mktime(date("H"), date("i") - 10, date("s"), date("n"), date("j"), date("Y"))),
		];
	}

	public static function saveTask($idProject, $taskDone = 1, $result = null) {
		$sqlResult = ($result)
		?	" , s.`A_SYNC_RESULT` = " . DB::getPDO()->quote( json_encode($result) )
		:	'';
		$sql = "
			update `_SYNC_FILE_PERMS` as s
			set
				s.`A_LAST_SYNC` = NOW()
				, s.`A_SYNCHRONIZED` = {$taskDone}
				{$sqlResult}
			where s.`ID_PROJECT` = {$idProject}
		";
		DB::getPDO()->execSql($sql);
	}

	public static function executeTask(&$task) {
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">executeTask (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($task);echo'</pre>';}

		$confTblName = 'IN7_MK_BAZA_DYSTRYBUCJI_COLUMN';
		$PROJ_mount_point = FoldersConfig::get($confTblName, 'mount_point');

		$project = new stdClass();
		$project->ID = $task->ID_PROJECT;
		$project->L_APPOITMENT_USER = $task->p__L_APPOITMENT_USER;
		$project->A_ADM_COMPANY = $task->p__A_ADM_COMPANY;
		$project->A_CLASSIFIED = $task->p__A_CLASSIFIED;
		$project->PARENT_L_APPOITMENT_USER = $task->p__PARENT_L_APPOITMENT_USER;
		$project->M_DIST_DESC = $task->p__M_DIST_DESC;
		$project->M_DISTRIBUTOR = $task->p__M_DISTRIBUTOR;
		$project->CRM_LISTA_ZASOBOW_ID = $task->p__CRM_LISTA_ZASOBOW_ID;

		$folderConf = FoldersConfig::getAll($confTblName);
		$uploader = new FileUploader($confTblName, $project);
		$errMsg = '';
		if (!$uploader->setConfig($folderConf, $errMsg)) {
			trigger_error("Error: folders config!", E_USER_NOTICE);
			die("Error: folders config!");
		}
		$uploader->findFolder();
		$mainFolder = $uploader->getDestFolder();
		if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">generated folderName('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';var_dump($uploader);echo'</pre>';}
		if (!$mainFolder) {
			$mainFolder = $uploader->generateFolderName();
			$mainFolderPath = rtrim($PROJ_mount_point, '/') . '/' . $mainFolder;
			if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">generated folderName('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($mainFolder, $mainFolderPath));echo'</pre>';}

			mkdir($mainFolderPath, 0770, true);
			@chmod($mainFolderPath, 0770);
			if (!file_exists($mainFolderPath)) {
				trigger_error("Error: Nie udało się utworzyć folderu! ({$mainFolder})", E_USER_NOTICE);
				return false;
			}
			$requirePermsExt = self::getRequiredPermsExtByRecord($project);
			$file = new stdClass();
			$file->name = '.';
			$file->path = '.';
			//$file->owner = '';
			//$file->group = '';
			//$file->perms = '';
			$file->isDir = true;
			$file->permsExt = array();
			$cmdList = self::fixFilePerms($file, $requirePermsExt, "{$PROJ_mount_point}/{$mainFolder}", $project);
			if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmdList-P('.$project->ID.') fixFilePerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}
		}
		else {
			$cmdList = self::fixProject($project, $mainFolder, $PROJ_mount_point, $task);
			if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmdList-P('.$project->ID.') fixProject (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}

			$mainFolderPath = rtrim($PROJ_mount_point, '/') . '/' . $mainFolder;
			$folderPismaPath = "{$mainFolderPath}/_PISMA";
			// if folder _PISMA not exists then create
			$cmdList[] = "if [ ! -d '{$folderPismaPath}' ] ; then mkdir '{$folderPismaPath}'; chmod 0770 '{$folderPismaPath}'; fi;";

			$cmdKorepList = self::fixProjectKoresp($project, $folderPismaPath);
			if (!empty($cmdKorepList)) {
				if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmdList-P('.$project->ID.') fixProjectKoresp (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdKorepList);echo'</pre>';}
				foreach ($cmdKorepList as $vCmd) {
					$cmdList[] = $vCmd;
				}
			}
		}
		if (!empty($cmdList)) {
			foreach ($cmdList as $vCmd) {
				if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Exec-cmdList-P('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vCmd);echo'</pre>';}
				exec($vCmd, $out, $outValue);
				if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Done-cmdList-P('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($outValue, $out));echo'</pre>';}
				if ($outValue != 0) {
					trigger_error("Error-cmd: {$vCmd}", E_USER_NOTICE);
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">';print_r("executeTask finished OK");echo'</pre>';}
	}

	public static function getActiveLoginList() {
		static $activeLoginList;
		if (!is_array($activeLoginList)) {
			$activeLoginList = DB::getPDO()->fetchValuesList("
				select u.`ADM_ACCOUNT`
				from `ADMIN_USERS` as u
				where u.`A_STATUS` =  'NORMAL'
			");
		}
		return $activeLoginList;
	}

	public static function isActiveUser($login) {
		$activeLoginList = self::getActiveLoginList();
		return in_array($login, $activeLoginList);
	}

	public static function getRequiredPermsExtByRecord($project) {
		// TODO: need to check if user is correct in field L_APPOITMENT_USER
		$requirePermsExt = array();
		if (!$project->L_APPOITMENT_USER && !$project->A_ADM_COMPANY) {// no owner, no write group
			// TODO: group PODMIOT
			$requirePermsExt["group:workgroup"] = array('write', 'read');
		}
		else {
			if ($project->L_APPOITMENT_USER) {
				if (self::isActiveUser($project->L_APPOITMENT_USER)) {
					$requirePermsExt["user:{$project->L_APPOITMENT_USER}"] = array('write', 'read');
				} else {
					// TODO: trigger error $project->L_APPOITMENT_USER is not active user!
				}
			}

			if (!empty($project->A_ADM_COMPANY) && $project->A_ADM_COMPANY == $project->A_CLASSIFIED) {
				$requirePermsExt["group:{$project->A_ADM_COMPANY}"] = array('read', 'write');
			}
			else if (empty($project->A_ADM_COMPANY) && empty($project->A_CLASSIFIED)) {
				$requirePermsExt["group:workgroup"] = array('write', 'read');
			}
			else {
				if ($project->A_ADM_COMPANY) {
					$requirePermsExt["group:{$project->A_ADM_COMPANY}"] = array('write');
				}
				if ($project->A_CLASSIFIED) {
					$requirePermsExt["group:{$project->A_CLASSIFIED}"] = array('read');
				}
			}
		}

		// add parent owner
		if (!empty($project->PARENT_L_APPOITMENT_USER) && $project->PARENT_L_APPOITMENT_USER != $project->L_APPOITMENT_USER) {
			if (self::isActiveUser($project->PARENT_L_APPOITMENT_USER)) {
				$requirePermsExt["user:{$project->PARENT_L_APPOITMENT_USER}"] = array('write', 'read');
			} else {
				// TODO: trigger error $project->PARENT_L_APPOITMENT_USER is not active user!
			}
		}

		// add user:_www perms read, write
		$requirePermsExt["user:_www"] = array('write', 'read');

		return $requirePermsExt;
	}

	/**
	 * Used for files added/modified by samba/afp.
	 *
	 * @return TODO: object to store in task->A_SYNC_RESULT
	 */
	public static function fixLastModifiedFiles($task) {
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fixLastModifiedFiles (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("fixLastModifiedFiles");echo'</pre>';}

		$PROJ_mount_point = FoldersConfig::get('IN7_MK_BAZA_DYSTRYBUCJI_COLUMN', 'mount_point');
		$PISMA_mount_point = FoldersConfig::get('IN7_DZIENNIK_KORESP_COLUMN', 'mount_point');

		$foundProjectFiles = array();
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$lastRunDate (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($task->A_LAST_SYNC);echo'</pre>';}
		//exec("cd {$PROJ_mount_point}; find . -newermt '{$task->A_LAST_SYNC}' ", $foundPaths);
		exec("cd {$PROJ_mount_point}; find . -newermt '{$task->A_LAST_SYNC}' -type f -exec ls -le '{}' \;", $filesWithPerms);// TODO: head -{$lastTotal + 10000}| tail
		if (!empty($filesWithPerms)) {
			$filesWithPermsTree = array();
			$lastName = '';
			foreach ($filesWithPerms as $line) {
				if (substr($line, 0, 1) == '-') {// file name
					$fileParts = array();
					$filePartsArr = explode(' ', $line);
					$lastInd = 0;
					foreach ($filePartsArr as $vPart) {
						if ($lastInd > 7) {// file name
							$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
							$lastInd++;
						}
						else if (!empty($vPart) || $vPart === '0') {
							$fileParts[] = $vPart;
							$lastInd++;
						}
					}

					$file = new stdClass();

					if (!isset($fileParts[8])) {
						echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
						//continue;
					}

					$file->name = end($fileParts);
					$file->path = end($fileParts);
					//$file->owner = $fileParts[2];
					//$file->group = $fileParts[3];
					//$file->perms = $fileParts[0];
					$file->isDir = (substr($line, 0, 1) == 'd');
					$file->permsExt = array();
					$filesWithPermsTree[$file->name] = $file;
					$lastName = $file->name;
				}
				else if (substr($line, 0, 1) == ' ') {// extended perm line
					if ($lastName == '..') {
						continue;
					}
					$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
				}
				else {// parse error
					$lastName = '..';
				}
			}

			foreach ($filesWithPermsTree as $vFile) {
				$vFile->path = ltrim($vFile->path, './');
				$vFile->path = explode('/', $vFile->path);
				$vFile->projectFolder = array_shift($vFile->path);
				$vFile->path = implode('/', $vFile->path);
				$projId = explode('.', $vFile->projectFolder);
				$projId = reset($projId);
				if (is_numeric($projId) && $projId > 0) {
					$foundProjectFiles[$projId][] = $vFile;
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">filesWithPerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PROJ_mount_point}\n";print_r($filesWithPerms);echo'</pre>';}
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">foundProjectFiles (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PROJ_mount_point}\n";print_r($foundProjectFiles);echo'</pre>';}

		if (!empty($foundProjectFiles)) {
			$sql = "
				select p.`ID`
					, p.`L_APPOITMENT_USER`
					, p.`A_ADM_COMPANY`
					, p.`A_CLASSIFIED`
					, pp.`L_APPOITMENT_USER` as PARENT_L_APPOITMENT_USER
				from IN7_MK_BAZA_DYSTRYBUCJI as p
					left join IN7_MK_BAZA_DYSTRYBUCJI as pp on (pp.ID=p.P_ID)
				where p.`ID` in (" . implode(',', array_keys($foundProjectFiles)) . ")
			";
			$rows = DB::getPDO()->fetchAll($sql);
			foreach ($rows as $row) {
				$r = (object)$row;

				$requirePermsExt = self::getRequiredPermsExtByRecord($r);

				foreach ($foundProjectFiles[$r->ID] as $vFile) {
					$cmdList = self::fixFilePerms($vFile, $requirePermsExt, "{$PROJ_mount_point}/{$vFile->projectFolder}", $r);
					if (!empty($cmdList)) {
						if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}

						foreach ($cmdList as $vCmd) {
							if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Exec-cmdList-P('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vCmd);echo'</pre>';}
							exec($vCmd, $out, $outValue);
							if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Done-cmdList-P('.$project->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($outValue, $out));echo'</pre>';}
							if ($outValue != 0) {
								trigger_error("Error-cmd: {$vCmd}", E_USER_NOTICE);
							}
						}
					} else {
						if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList is empty P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("empty");echo'</pre>';}
					}
				}
			}
		}
	}

	public static function fixProjectKoresp($project, $folderPismaPath) {
		$PISMA_mount_point = FoldersConfig::get('IN7_DZIENNIK_KORESP_COLUMN', 'mount_point');
		$PISMA_mount_point = rtrim($PISMA_mount_point, '/');
		// $PISMA_mount_point / {TYP_KORESP} / {ID} . *
		$sql = "
			select k.`ID`
				, k.`K_TYP_KORESP`
			from `IN7_DZIENNIK_KORESP` as k
			where k.`ID_PROJECT`={$project->ID}
			order by k.`ID` DESC
			limit 100
		";
		$pismaList = array_map(function ($row) {
			return $row['K_TYP_KORESP']; // TODO: K_TYP_KORESP not used
		}, DB::getPDO()->fetchAllByKey($sql, 'ID'));
		// $PISMA_mount_point / {TYP_KORESP} / {ID} . *
		if (empty($pismaList)) {
			return false;
		}
		$lsCmd = array();
		$lsCmd[] = "0.*/";// if only one folder then it doesnt show folder name, only files
		foreach ($pismaList as $kID => $vTyp) {
			$lsCmd[] = "{$kID}.*/";
		}
		if (empty($lsCmd)) {
			return false;
		}
		$lsCmd = 'ls -le ' . implode($lsCmd, ' ') . ' 2>>/dev/null ';
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Koreps lsCmd (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($lsCmd);echo'</pre>';}

		exec("cd {$PISMA_mount_point}; {$lsCmd} ", $filesWithPerms);
		$filesWithPermsTree = array();
		if (!empty($filesWithPerms)) {
			$lastName = '';
			$folderParent = '';
			foreach ($filesWithPerms as $line) {
				if (substr($line, 0, 1) == '-') {// file name
					$fileParts = array();
					$filePartsArr = explode(' ', $line);
					$lastInd = 0;
					foreach ($filePartsArr as $vPart) {
						if ($lastInd > 7) {// file name
							$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
							$lastInd++;
						}
						else if (!empty($vPart) || $vPart === '0') {
							$fileParts[] = $vPart;
							$lastInd++;
						}
					}

					$file = new stdClass();

					if (!isset($fileParts[8])) {
						echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
						//continue;
					}

					$file->name = end($fileParts);
					$file->path = array();
					if ($folderParent) $file->path[] = $folderParent;
					$file->path[] = end($fileParts);
					$file->path = implode('/', $file->path);
					//$file->owner = $fileParts[2];
					//$file->group = $fileParts[3];
					//$file->perms = $fileParts[0];
					$file->isDir = (substr($line, 0, 1) == 'd');
					$file->permsExt = array();
					$filesWithPermsTree[$file->name] = $file;
					$lastName = $file->name;
				}
				else if (substr($line, 0, 1) == ' ') {// extended perm line
					if ($lastName == '..') {
						continue;
					}
					$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
				}
				else if (substr($line, -1) == ':') {// folder
					$folderParent = trim(substr($line, 0, -1), '/');
				}
				else if (substr($line, 0, 5) == 'total') {
					continue;
				}
				else if (empty($line)) {
					$folderParent = '';
					continue;
				}
				else {// parse error
					$lastName = '..';
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">filesWithPerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PISMA_mount_point}\n";print_r($filesWithPerms);echo'</pre>';}
		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">filesWithPermsTree (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PISMA_mount_point}\n";print_r($filesWithPermsTree);echo'</pre>';}

		$cmdList = array();
		$requirePermsExt = self::getRequiredPermsExtByRecord($project);
		foreach ($filesWithPermsTree as $kFileName => $vFile) {
			$cmdFile = self::fixFilePerms($vFile, $requirePermsExt, "{$PISMA_mount_point}", $project);
			if (!empty($cmdFile)) {
				foreach ($cmdFile as $vCmd) {
					$cmdList[] = $vCmd;
				}
			}

			// if folder _PISMA not exists then create
			$fname = explode('/', $vFile->path);
			$fname = end($fname);
			$cmdList []= "if [ ! -f '{$folderPismaPath}/{$fname}' ] ; then ln '{$PISMA_mount_point}/{$vFile->path}' '{$folderPismaPath}'/; fi; ";
		}
		return $cmdList;
	}

	/*
	 * Not used.
	 */
	public static function preloadFixProject(&$task, $mainFolder, $PROJ_mount_point) {
		if ($task->ID_PROJECT == 1695) {// TODO: biall-net - 8853 pliki w katalog - stary HANDLOWY

			if (isset($task->A_SYNC_RESULT->total)) {
				exec("cd '{$PROJ_mount_point}/{$mainFolder}' && find . -type f | wc -l ", $filesTotal);
				if (!empty($filesTotal)) {
					$filesTotal = reset($filesTotal);
					$filesTotal = intval(trim($filesTotal));
					$task->A_SYNC_RESULT->total = $filesTotal;
					self::saveTask($task->ID_PROJECT, 0, $task->A_SYNC_RESULT);
				}
			}
		}
	}

	public static function fixProject($r, $mainFolder, $PROJ_mount_point, &$task) {
		$time = time();
		if ($r->ID == 1695) {// TODO: biall-net - 8853 pliki w katalog - stary HANDLOWY
			if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: pomin - za dużo plików ('.$r->ID.'/'.$mainFolder.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($r);echo'</pre>';}
			//return false;
		}

		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">r('.$r->ID.'/'.$mainFolder.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($r);echo'</pre>';}

		$requirePermsExt = self::getRequiredPermsExtByRecord($r);

		$filesWithPerms = array();
		$cmd = " ls -Rlea . ";
		if (false) {//$r->ID == 1695) {// TODO: biall-net - 8853 pliki w katalog - stary HANDLOWY
			if (isset($task->A_SYNC_RESULT->total)) {
				if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">A_SYNC_RESULT (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("A_SYNC_RESULT[total]={$task->A_SYNC_RESULT->total}");echo'</pre>';}
				if ($task->A_SYNC_RESULT->total > 1000) {
					$lastDone = V::get('lastDone', 0, $task->A_SYNC_RESULT, 'int');
					if ($lastDone > 0) {
						$task->A_SYNC_RESULT->lastDone = $lastDone + 1000;
						$cmd = " find . -type f | head -" . ($lastDone + 1000) . " | tail -1000 | xargs ls -le ";
					} else {
						$task->A_SYNC_RESULT->lastDone = 1000;
						$cmd = " find . -type f | head -1000 | xargs ls -le ";
					}
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("cd '{$PROJ_mount_point}/{$mainFolder}' && {$cmd} ");echo'</pre>';}
		exec("cd '{$PROJ_mount_point}/{$mainFolder}' && {$cmd} ", $filesWithPerms);
		if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$filesWithPerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($filesWithPerms);echo'</pre>';}
		$filesWithPermsTree = array();

		$lastName = '';
		$folderParent = '';
		foreach ($filesWithPerms as $line) {
			if (substr($line, 0, 1) == '-' || substr($line, 0, 1) == 'd') {// file name
				$fileParts = array();
				$filePartsArr = explode(' ', $line);
				$lastInd = 0;
				foreach ($filePartsArr as $vPart) {
					if ($lastInd > 7) {// file name
						$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
						$lastInd++;
					}
					else if (!empty($vPart) || $vPart === '0') {
						$fileParts[] = $vPart;
						$lastInd++;
					}
				}

				if (end($fileParts) == '..' || (end($fileParts) == '.' && $folderParent)) {
					$lastName = '..';
					continue;
				}

				$file = new stdClass();

				if (!isset($fileParts[8])) {
					echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
					//continue;
				}

				$file->name = end($fileParts);
				$file->path = array();
				if ($folderParent) $file->path[] = $folderParent;
				$file->path[] = end($fileParts);
				$file->path = implode('/', $file->path);
				//$file->owner = $fileParts[2];
				//$file->group = $fileParts[3];
				//$file->perms = $fileParts[0];
				$file->isDir = (substr($line, 0, 1) == 'd');
				$file->permsExt = array();
				$filesWithPermsTree[$file->name] = $file;
				$lastName = $file;
			}
			else if (substr($line, 0, 1) == ' ') {// extended perm line
				if ($lastName == '..') {
					continue;
				}
				$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
			}
			else if (substr($line, 0, 2) == './' && substr($line, -1) == ':') {// folder
				$folderParent = substr($line, 2, -1);
			}
			else if (substr($line, 0, 5) == 'total') {
				continue;
			}
			else if (empty($line)) {
				$folderParent = '';
				continue;
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$filesWithPermsTree (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($filesWithPermsTree);echo'</pre>';}

		// check perms
		/*
files:
W		read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown
W		read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity
R		read,      execute,       readattr,          readextattr,             readsecurity

dir:
W		list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown,file_inherit,directory_inherit
W		list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,                    file_inherit,directory_inherit
R		list,         search,                              readattr,          readextattr,             readsecurity,                    file_inherit,directory_inherit
		 */
		$cmdList = array();
		foreach ($filesWithPermsTree as $kFileName => $vFile) {
			if (false) {
				// TODO: $kFileName == '_PISMA' - readonly
				// TODO: $vFile->path => _PISMA/...
			}
			else {
				$cmdFile = self::fixFilePerms($vFile, $requirePermsExt, "{$PROJ_mount_point}/{$mainFolder}", $r);
				if (!empty($cmdFile)) {
					foreach ($cmdFile as $vCmd) {
						$cmdList[] = $vCmd;
					}
				}
			}
		}
		return $cmdList;
	}

	public static function fixFilePerms($vFile, $requirePermsExt, $mainPath, $r) {
		//trigger_error("fixFilePerms: " . json_encode($vFile), E_USER_NOTICE);
		$cmdList = array();
		$vReqPermsExt = array();
		foreach ($requirePermsExt as $kPermObj => $vPerms) {
			$vReqPermsExt[$kPermObj] = array();
			foreach ($vPerms as $vPerm) {
				if ($vFile->isDir) {
					if ($vPerm == 'read') {
						$vReqPermsExt[$kPermObj]['list'] = 0;
						$vReqPermsExt[$kPermObj]['search'] = 0;
						$vReqPermsExt[$kPermObj]['readattr'] = 0;
						$vReqPermsExt[$kPermObj]['readextattr'] = 0;
						$vReqPermsExt[$kPermObj]['readsecurity'] = 0;
						$vReqPermsExt[$kPermObj]['file_inherit'] = 0;
						$vReqPermsExt[$kPermObj]['directory_inherit'] = 0;
					}
					else if ($vPerm == 'write') {
						$vReqPermsExt[$kPermObj]['add_file'] = 0;
						$vReqPermsExt[$kPermObj]['add_subdirectory'] = 0;
						$vReqPermsExt[$kPermObj]['delete_child'] = 0;
						$vReqPermsExt[$kPermObj]['writeattr'] = 0;
						$vReqPermsExt[$kPermObj]['writeextattr'] = 0;
						$vReqPermsExt[$kPermObj]['writesecurity'] = 0;
						$vReqPermsExt[$kPermObj]['chown'] = 0;
					}
				}
				else {
					if ($vPerm == 'read') {
						$vReqPermsExt[$kPermObj]['read'] = 0;
						$vReqPermsExt[$kPermObj]['execute'] = 0;
						$vReqPermsExt[$kPermObj]['readattr'] = 0;
						$vReqPermsExt[$kPermObj]['readextattr'] = 0;
						$vReqPermsExt[$kPermObj]['readsecurity'] = 0;
					}
					else if ($vPerm == 'write') {
						$vReqPermsExt[$kPermObj]['write'] = 0;
						$vReqPermsExt[$kPermObj]['append'] = 0;
						$vReqPermsExt[$kPermObj]['writeattr'] = 0;
						$vReqPermsExt[$kPermObj]['writeextattr'] = 0;
						$vReqPermsExt[$kPermObj]['writesecurity'] = 0;
						$vReqPermsExt[$kPermObj]['chown'] = 0;
					}
				}
			}
		}

		$permsToRemove = array();
		foreach ($vFile->permsExt as $vPermExtLine) {
			$vPermExtArr = explode(' ', $vPermExtLine);
			$vPermExt = new stdClass();
			$vPermExt->nr = trim($vPermExtArr[0], ' :');
			$vPermExt->permObj = $vPermExtArr[1];
			$vPermExt->perms = '';
			if (count($vPermExtArr) == 4) {
				$vPermExt->perms = explode(',', $vPermExtArr[3]);
			}
			else if (count($vPermExtArr) == 5 && $vPermExtArr[2] == 'inherited') {
				$vPermExt->perms = explode(',', $vPermExtArr[4]);
			}
			else {
				echo "Error: Perm Ext Line format! ({$vPermExtLine})";
				continue;
			}

			$permFound = false;
			foreach ($vReqPermsExt as $kPermObj => $vPerms) {
				if (false !== strpos($vPermExtLine, $kPermObj)) {
					foreach ($vPerms as $kPerm => $vPermChecked) {
						if (in_array($kPerm, $vPermExt->perms)) {
							$vReqPermsExt[$kPermObj][$kPerm] = 1;
							$permFound = true;
						}
					}
				}
			}
			if (!$permFound) {
				$permsToRemove[] = $vPermExt->nr;
			}
			//echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$vReqPermsExt('.$vFile->name.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vReqPermsExt);echo'</pre>';
		}

		if (!empty($permsToRemove)) {
			if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:300px;overflow:auto;border:1px solid red;text-align:left;">TO-rm('.$r->ID.'): $permsToRemove('.$vFile->name.') isDir('.$vFile->isDir.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('permsToRemove'=>$permsToRemove, 'vFile->permsExt'=>$vFile->permsExt, 'r'=>$r));echo'</pre>';}
			$permsToRemove = array_reverse($permsToRemove);
			foreach ($permsToRemove as $vPermInd) {
				$cmdList []= "chmod -a# {$vPermInd} '{$mainPath}/{$vFile->path}' ";
			}
		}

		$permsToAdd = array();
		foreach ($vReqPermsExt as $kPermObj => $vPerms) {
			foreach ($vPerms as $kPerm => $vPermChecked) {
				if (!$vPermChecked) {
					$permsToAdd[$kPermObj][$kPerm] = 1;
				}
			}
		}
		if (!empty($permsToAdd)) {
			if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:300px;overflow:auto;border:1px solid red;text-align:left;">TO-add('.$r->ID.'): $permsToAdd('.$vFile->name.') isDir('.$vFile->isDir.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('permsToAdd'=>$permsToAdd, 'vFile->permsExt'=>$vFile->permsExt, 'r'=>$r));echo'</pre>';}
			foreach ($permsToAdd as $kPermObj => $vPerms) {
				$cmdList []= "chmod +a \"{$kPermObj} allow " . implode(',', array_keys($vPerms)) . "\" '{$mainPath}/{$vFile->path}' ";
			}
		}
		return $cmdList;
	}

	/**
	 * @returns array - last modified project ids.
	 */
	public static function fixLastModifiedByFiles() {// TODO: RMME
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fixLastModifiedByFiles (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("fixLastModifiedByFiles");echo'</pre>';}

		/*
		 * $ man find:
			-newer file
					True if the current file has a more recent last modification time than file.

			-newerXY file
					True if the current file has a more recent last access time (X=a), inode creation time (X=B), change time (X=c),
					or modification time (X=m) than the last access time (Y=a), inode creation time (Y=B), change time (Y=c), or mod-
					ification time (Y=m) of file.  In addition, if Y=t, then file is instead interpreted as a direct date specifica-
					tion of the form understood by cvs(1).  Note that -newermm is equivalent to -newer.
		 *
		 * $ man chmod:
			 -i      Removes the 'inherited' bit from all entries in the named file(s) ACLs.
		 *
		 * chmod -a "admin allow write" file1  # remove line
		 * chmod -a# 1 file1                   # remove line at position 1
		 *
		 * chmod +a nie zmienia modify date: find . -newermt '2013-10-07 ...'
		 */
		// find /Library/Server/Web/Data/Sites/Default/PLIKI/PROJEKTY -newermt '2013-10-07 16:20'

		$PROJ_mount_point = FoldersConfig::get('IN7_MK_BAZA_DYSTRYBUCJI_COLUMN', 'mount_point');
		$PISMA_mount_point = FoldersConfig::get('IN7_DZIENNIK_KORESP_COLUMN', 'mount_point');

		$foundProjectIds = array();
		$lastRunDate = self::getLastRunDate();
		exec("cd {$PROJ_mount_point}; find . -newermt '{$lastRunDate}' ", $foundPaths);
		if (!empty($foundPaths)) {
			foreach ($foundPaths as $path) {
				$path = ltrim($path, './');
				$folder = explode('/', $path);
				$folder = reset($folder);
				$projId = explode('.', $folder);
				$projId = reset($projId);
				if (is_numeric($projId)) {
					$foundProjectIds[$projId] = $folder;
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">foundPaths (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PROJ_mount_point}\n";print_r($foundPaths);echo'</pre>';}
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">foundProjectIds (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$PROJ_mount_point}\n";print_r($foundProjectIds);echo'</pre>';}

		if (!empty($foundProjectIds)) {
			$sql = "
				select p.`ID`
					, p.`L_APPOITMENT_USER`
					, p.`A_ADM_COMPANY`
					, p.`A_CLASSIFIED`
					, pp.`L_APPOITMENT_USER` as PARENT_L_APPOITMENT_USER
				from IN7_MK_BAZA_DYSTRYBUCJI as p
					left join IN7_MK_BAZA_DYSTRYBUCJI as pp on (pp.ID=p.P_ID)
				where p.`ID` in (" . implode(',', array_keys($foundProjectIds)) . ")
			";
			$rows = DB::getPDO()->fetchAll($sql);
			foreach ($rows as $row) {
				$r = (object)$row;
				$mainFolder = $foundProjectIds[$r->ID];

				$cmdList = self::fixProject($r, $mainFolder, $PROJ_mount_point);
				if (!empty($cmdList)) {
					if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}
				} else {
					if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}
				}
			}
		}
		return array_keys($foundProjectIds);
	}

	public static function fixLastModifiedByDB($fixedProjIds) {// TODO: RMME
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fixLastModifiedByDB (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("fixLastModifiedByDB");echo'</pre>';}

		$sqlWhereAdd = '';
		if (!empty($fixedProjIds)) {
			$sqlWhereAdd = " and p.`ID` not in (" . implode(',', $fixedProjIds) . ") ";
		}

		$lastRunDate = self::getLastRunDate();
		$sql = "
			select p.`ID`
				, p.`L_APPOITMENT_USER`
				, p.`A_ADM_COMPANY`
				, p.`A_CLASSIFIED`
				, pp.`L_APPOITMENT_USER` as PARENT_L_APPOITMENT_USER
			from IN7_MK_BAZA_DYSTRYBUCJI as p
				left join IN7_MK_BAZA_DYSTRYBUCJI as pp on (pp.ID=p.P_ID)
			where
				(	p.`A_RECORD_UPDATE_DATE`>'{$lastRunDate}'
					or p.`A_RECORD_CREATE_DATE`>'{$lastRunDate}'
				)
				{$sqlWhereAdd}
		";
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">sql (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sql);echo'</pre>';}
		$projectList = array_map(function ($row) {
			return (object)$row;
		}, DB::getPDO()->fetchAllByKey($sql, 'ID'));

		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">projectList (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($projectList);echo'</pre>';}

	}

	public static function getLastRunDate() {// TODO: RMME
		if (($argFromDate = V::get('_fromDate', '', $_GET)) != '') {
			// Format: 2013-10-07 23:46
			if (strlen($argFromDate) == 16
					&& substr($argFromDate, 4, 1) == '-'
					&& substr($argFromDate, 7, 1) == '-'
					&& substr($argFromDate, 10, 1) == '-'
					&& substr($argFromDate, 13, 1) == ':'
					&& is_numeric(str_replace(array('-', ':'), '', $argFromDate))
			) {
				$lastRunDate = mktime(substr($argFromDate, 11, 2), substr($argFromDate, 14, 2), 0, substr($argFromDate, 5, 2), substr($argFromDate, 8, 2), substr($argFromDate, 0, 4));
				$lastRunDate = date("Y-m-d H:i", $lastRunDate);
			}
		}
		if (!$lastRunDate) {
			$lastRunDate = mktime(date("H") - 1, date("i"), date("s"), date("n"), date("j"), date("Y"));
			$lastRunDate = date("Y-m-d H:i", $lastRunDate);
		}
		//$lastRunDate = '2013-09-01 01:00';
		echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">lastRunDate (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($lastRunDate);echo'</pre>';
		return $lastRunDate;
	}

	public static function usersAction() {
		$task = self::getTaskNewerUsers();

		$users = self::getUsersNewerThen($task->A_LAST_SYNC);

		echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$users (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($users);echo'</pre>';

		self::saveTask(-2);
	}

	public static function usersAllAction() {
		/*
		 * DBU - DB User       LDU - LDAP User
		 * DBG - DB Group      LDG - LDAP Group
		 *
		 * DBU - ADMIN_USERS
		 * DBG - CRM_LISTA_ZASOBOW where TYPE in('STANOWISKO','PODMIOT','DZIAL')// TODO: GRUPA_PROJEKTOWA - ldap nested group?
		 *
		 * 1. fetch DB Users
		 *   ? only active users (A_STATUS == 'NORMAL')
		 * 2. fetch LDAP Users
		 *
		 * DBU.ADM_ACCOUNT == LDU.uid
		 * DBG.A_LDAP_GID == LDG.gidNumber
		 *
		 * TODO: $group createLdapGroup()
		 *
		 */
		Lib::loadClass('UsersHelper');
		$users = self::getUsersAll();
		echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">users count('.count($users).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($users);echo'</pre>';
		$allLdapUsers = UsersHelper::getLDAPUsersAll();
		$ldapUsers = array();
		foreach ($allLdapUsers as $vLdapUser) {
			$ldapUsers[$vLdapUser->uid] = $vLdapUser;
		}
		unset($allLdapUsers);
		echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">ldapUsers count('.count($ldapUsers).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($ldapUsers);echo'</pre>';

		foreach ($users as $kLogin => $vUser) {
			if ($vUser->A_STATUS == 'NORMAL') {
				if (array_key_exists($vUser->ADM_ACCOUNT, $ldapUsers)) {
					echo '<div>'.$vUser->ADM_ACCOUNT.': OK? account exists in LDAP</div>';
				}
				else {
					echo '<div>'.$vUser->ADM_ACCOUNT.': TODO: create account in LDAP</div>';
				}
			}
			else if ($vUser->A_STATUS == 'DELETED') {
				if (array_key_exists($vUser->ADM_ACCOUNT, $ldapUsers)) {
					echo '<div>'.$vUser->ADM_ACCOUNT.': TODO: disable account</div>';
				}
				else {
					//echo '<div>'.$vUser->ADM_ACCOUNT.': WARNING: account not exists in LDAP</div>';
				}
			}
		}
	}

	public static function getUsersNewerThen($datetime) {
		$sqlWhereAdd = "";
		if ($datetime) {
			$sqlWhereAdd = " and ( u.`A_RECORD_CREATE_DATE`>'{$datetime}' or u.`A_RECORD_UPDATE_DATE`>'{$datetime}' )";
		} else {
			// TODO: error datetime not set
		}
		$sql = "
			select u.ID
				, u.`ADM_ACCOUNT`
				, u.`ADM_NAME`
				, u.`A_STATUS`
			from `ADMIN_USERS` as u
			where
				u.`A_STATUS` in('NORMAL', 'DELETED')
		";
		return array_map(function ($row) {
			return (object)$row;
		}, DB::getPDO()->fetchAllByKey($sql, 'ADM_ACCOUNT'));
	}

	public static function getUsersAll() {
		$sql = "
			select u.ID
				, u.`ADM_ACCOUNT`
				, u.`ADM_NAME`
				, u.`A_STATUS`
			from `ADMIN_USERS` as u
			where
				u.`A_STATUS` in('NORMAL', 'DELETED')
		";
		return array_map(function ($row) {
			return (object)$row;
		}, DB::getPDO()->fetchAllByKey($sql, 'ADM_ACCOUNT'));
	}

}


class SyncPermsCustomTables {

	public $_tbls;
	private $_db;
	private $_syncTblName = '_SYNC_TABLE_FILE_PERMS';
	private $_activeLoginList;

	public function run() {
		if (!$this->fetchConfig()) return;

		$this->installTable();
		$this->generateTasksNewRecords();
		$this->generateTasksUpdated();
		$this->newFilesAction();
		$limit = 100;
		for ($i = 0; $i < $limit; $i++) {
			$loopTasksFired = 0;
			foreach ($this->_tbls as $tblName => $tblConf) {
				$task = $this->nextTaskAction($tblName);
				if (!$task) {
					// TODO: rm $tblName from config to prevent searching for task in next loops
					unset($this->_tbls[$tblName]);
					continue;
				}
				$loopTasksFired++;
			}
			if (!$loopTasksFired) break;
			$i += $loopTasksFired;
		}
	}

	public function fetchConfig() {
		$sql = "
			select c.`CONF_VAL` as tbl_names
			from `CRM_CONFIG` as c
			where c.`CONF_KEY`='_SYNC_TABLE_FILE_PERMS__TablesList'
		";
		$tblNames = DB::getPDO()->fetchValue($sql);
		$tbls = (!empty($tblNames)) ? explode("\n", $tblNames) : [];
		$configTbls = array();
		Lib::loadClass('FoldersConfig');
		if (!empty($tbls)) {
			$folderConfAll = FoldersConfig::getRawData();
			foreach ($tbls as $tblName) {
				$tblName = trim($tblName);
				if (empty($tblName)) continue;
				$confTblName = "{$tblName}_COLUMN";
				if (FoldersConfig::hasConfig($confTblName)) {
					$configTbls[$tblName] = FoldersConfig::getAll($confTblName);
				}
			}
		}
		//echo "DBG:" . __LINE__ . ":" . __FUNCTION__ . "(): tbls [" . implode(',', array_keys($configTbls)) . "]\n";
		if (!empty($configTbls)) {
			$this->_tbls = $configTbls;
		}
		return $this->_tbls;
	}

	public function installTable() {
		if (!$this->_tbls) return;
		$sql = "
			CREATE TABLE IF NOT EXISTS `{$this->_syncTblName}` (
				`ID` int(11) NOT NULL AUTO_INCREMENT,
				`TBL_NAME` varchar(64),
				`TBL_ID` int(11) NOT NULL,
				`L_APPOITMENT_USER` varchar(64) DEFAULT '',
				`A_ADM_COMPANY` varchar(64) NOT NULL DEFAULT '',
				`A_CLASSIFIED` varchar(64) NOT NULL DEFAULT '',
				`PARENT_L_APPOITMENT_USER` varchar(64) NOT NULL DEFAULT '',
				`A_LAST_SYNC` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
				`A_SYNCHRONIZED` tinyint(4) NOT NULL DEFAULT '0',
				`A_SYNC_RESULT` varchar(255) NOT NULL DEFAULT '',
				`A_POMIN_SYNC` tinyint(1) NOT NULL DEFAULT '0',
				PRIMARY KEY (`ID`),
				UNIQUE KEY `TBL` (`TBL_NAME`, `TBL_ID`)
			) ENGINE=MyISAM  DEFAULT CHARSET=latin2 ;
		";
		try {
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}
	}

	public function generateTasksNewRecords() {
		if (!$this->_tbls) return;

		$sqlValues = array();
		foreach ($this->_tbls as $tblName => $tblConf) {
			$sqlValues[] = "('{$tblName}', -1, '', '', '', '')";
		}
		$sqlValues = implode(',', $sqlValues);
		$sql = "
			insert ignore into `{$this->_syncTblName}` (`TBL_NAME`, `TBL_ID`, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`, `PARENT_L_APPOITMENT_USER`)
			values {$sqlValues};
		";
		try {
			DB::getPDO()->execSql($sql);
		} catch (Exception $e) {
			echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
			trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
		}

		foreach ($this->_tbls as $tblName => $tblConf) {
			$sql = "
				insert ignore into `{$this->_syncTblName}` (`TBL_NAME`, `TBL_ID`, `L_APPOITMENT_USER`, `A_ADM_COMPANY`, `A_CLASSIFIED`, `PARENT_L_APPOITMENT_USER`)
				select '{$tblName}' as TBL_NAME
					, t.`ID`, t.`L_APPOITMENT_USER`, t.`A_ADM_COMPANY`, t.`A_CLASSIFIED`
					, '' as PARENT_L_APPOITMENT_USER	-- pp.`L_APPOITMENT_USER`
				from `{$tblName}` as t
				where t.`A_STATUS`!='DELETED'
			";
			try {
				DB::getPDO()->execSql($sql);
			} catch (Exception $e) {
				echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
				trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
			}
		}
	}

	public function generateTasksUpdated() {
		if (!$this->_tbls) return;

		foreach ($this->_tbls as $tblName => $tblConf) {
			$sql = "update `{$this->_syncTblName}` as s
					, `{$tblName}` as t
					-- , `IN7_MK_BAZA_DYSTRYBUCJI` as pt
				set
					s.`L_APPOITMENT_USER`=t.`L_APPOITMENT_USER`
					, s.`A_ADM_COMPANY`=t.`A_ADM_COMPANY`
					, s.`A_CLASSIFIED`=t.`A_CLASSIFIED`
					, s.`PARENT_L_APPOITMENT_USER`=''  -- pt.`L_APPOITMENT_USER`
					, s.`A_SYNCHRONIZED`=0
				where
					s.`TBL_ID`=t.`ID`
					and s.`TBL_NAME`='{$tblName}'
			--		 and pt.`ID`=t.`P_ID`
					and (
						s.`L_APPOITMENT_USER`!=t.`L_APPOITMENT_USER`
						or s.`A_ADM_COMPANY`!=t.`A_ADM_COMPANY`
						or s.`A_CLASSIFIED`!=t.`A_CLASSIFIED`
			--			or s.`PARENT_L_APPOITMENT_USER`!=pt.`L_APPOITMENT_USER`
					)
			";
			try {
				DB::getPDO()->execSql($sql);
			} catch (Exception $e) {
				echo "DB ERR L." . __LINE__ . ": " . $e->getMessage() . "\n";
				trigger_error("DB ERR L." . __LINE__ . ": " . $e->getMessage(), E_USER_WARNING);
			}
		}
	}

	public function newFilesAction() {
		if (!$this->_tbls) return;

		foreach ($this->_tbls as $tblName => $tblConf) {
			$task = $this->_getTaskNewFiles($tblName);
			$taskResult = $this->_fixLastModifiedFiles($task);
			$this->_saveTask($tblName, -1);
		}
	}

	private function _getTaskNewFiles($tblName) {
		$task = null;

		$sql = "
			select s.*
			from `{$this->_syncTblName}` as s
			where s.`TBL_ID`=-1 and s.`TBL_NAME`='{$tblName}'
		";
		$task = DB::getPDO()->fetchFirst($sql);
		return ($task)
		? (object)$task
		: (object)[
			'TBL_NAME' => $tblName,
			'TBL_ID' => -1,
			'A_SYNC_RESULT' => new stdClass(),
			'A_LAST_SYNC' => date("Y-m-d H:i", mktime(date("H"), date("i") - 10, date("s"), date("n"), date("j"), date("Y"))),
		];
	}

	private function _fixLastModifiedFiles($task) {
		$tblName = $task->TBL_NAME;

		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fixLastModifiedFiles (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("fixLastModifiedFiles");echo'</pre>';}

		$confTblName = "{$task->TBL_NAME}_COLUMN";
		$TBL_mount_point = FoldersConfig::get($confTblName, 'mount_point');
		$record = null;
		$folderConf = FoldersConfig::getAll($confTblName);
		$uploader = new FileUploader($confTblName, $record);
		if (!$uploader->setConfig($folderConf, $errMsg)) {
			trigger_error("Error: folders config!", E_USER_NOTICE);
			die("Error: folders config!");
		}
		$hasRecordOwnFolder = $uploader->hasRecordOwnFolder();

		$foundTblFiles = array();
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$lastRunDate (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($task->A_LAST_SYNC);echo'</pre>';}
		//exec("cd {$TBL_mount_point}; find . -newermt '{$task->A_LAST_SYNC}' ", $foundPaths);
		$filesWithPerms = array();
		$cmd = "cd {$TBL_mount_point}; find . -newermt '{$task->A_LAST_SYNC}' -type f -exec ls -le '{}' \;";
		exec($cmd, $filesWithPerms);// TODO: head -{$lastTotal + 10000}| tail
		if (!empty($filesWithPerms)) {
			$filesWithPermsTree = array();
			$lastName = '';
			foreach ($filesWithPerms as $line) {
				if (substr($line, 0, 1) == '-') {// file name
					$fileParts = array();
					$filePartsArr = explode(' ', $line);
					$lastInd = 0;
					foreach ($filePartsArr as $vPart) {
						if ($lastInd > 7) {// file name
							$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
							$lastInd++;
						}
						else if (!empty($vPart) || $vPart === '0') {
							$fileParts[] = $vPart;
							$lastInd++;
						}
					}

					$file = new stdClass();

					if (!isset($fileParts[8])) {
						echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
						//continue;
					}

					$file->name = end($fileParts);
					$file->path = end($fileParts);
					//$file->owner = $fileParts[2];
					//$file->group = $fileParts[3];
					//$file->perms = $fileParts[0];
					$file->isDir = (substr($line, 0, 1) == 'd');
					$file->permsExt = array();
					$filesWithPermsTree[$file->name] = $file;
					$lastName = $file->name;
				}
				else if (substr($line, 0, 1) == ' ') {// extended perm line
					if ($lastName == '..') {
						continue;
					}
					$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
				}
				else {// parse error
					$lastName = '..';
				}
			}

			foreach ($filesWithPermsTree as $vFile) {
				$vFile->path = ltrim($vFile->path, './');
				$vFile->path = explode('/', $vFile->path);
				$vFile->recordFolder = array_shift($vFile->path);// $vFile->projectFolder -> recordFolder
				$vFile->path = implode('/', $vFile->path);
				// TODO: _getTableIdFromFolderName($task->TBL_NAME, $vFile->recordFolder);

				$tblId = 0;
				if ($hasRecordOwnFolder) {
					$tblId = explode('.', $vFile->recordFolder);
					$tblId = reset($tblId);
				}
				else {
					$tblId = explode('.', $vFile->path);
					$tblId = reset($tblId);
				}

				if (is_numeric($tblId) && $tblId > 0) {
					$foundTblFiles[$tblId][] = $vFile;
				}
			}
		}
		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">filesWithPerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$TBL_mount_point}\n";print_r($filesWithPerms);echo'</pre>';}
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">foundTblFiles (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): '."path: {$TBL_mount_point}\n";print_r($foundTblFiles);echo'</pre>';}

		if (!empty($foundTblFiles)) {
			$rowsIds = array_keys($foundTblFiles);
			$sql = "
				select t.`ID`
					, t.`L_APPOITMENT_USER`
					, t.`A_ADM_COMPANY`
					, t.`A_CLASSIFIED`
			--		, pt.`L_APPOITMENT_USER` as PARENT_L_APPOITMENT_USER
				from `{$task->TBL_NAME}` as t
			--		left join `{$task->TBL_NAME}` as pt on (pt.ID=t.P_ID)
				where t.`ID` in (" . implode(',', $rowsIds) . ")
			";
			$rows = DB::getPDO()->fetchAll($sql);
			foreach ($rows as $row) {
				$r = (object)$row;

				$requirePermsExt = $this->_getRequiredPermsExtByRecord($r);

				foreach ($foundTblFiles[$r->ID] as $vFile) {
					$cmdList = $this->_fixFilePerms($vFile, $requirePermsExt, "{$TBL_mount_point}/{$vFile->recordFolder}", $r);
					if (!empty($cmdList)) {
						if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}

						foreach ($cmdList as $vCmd) {
							if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Exec-cmdList-P('.$r->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vCmd);echo'</pre>';}

							exec($vCmd, $out, $outValue);

							if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Done-cmdList-P('.$r->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($outValue, $out));echo'</pre>';}
							if ($outValue != 0) {
								trigger_error("Error-cmd: {$vCmd}", E_USER_NOTICE);
							}
						}
					} else {
						if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$cmdList is empty P('.$r->ID.') total('.count($cmdList).') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r("empty");echo'</pre>';}
					}
				}
			}
		}

	}

	public function _getRequiredPermsExtByRecord($record) {
		// TODO: need to check if user is correct in field L_APPOITMENT_USER
		$requirePermsExt = array();
		if (!$record->L_APPOITMENT_USER && !$record->A_ADM_COMPANY) {// no owner, no write group
			// TODO: group PODMIOT
			$requirePermsExt["group:workgroup"] = array('write', 'read');
		}
		else {
			if ($record->L_APPOITMENT_USER) {
				if ($this->_isActiveUser($record->L_APPOITMENT_USER)) {
					$requirePermsExt["user:{$record->L_APPOITMENT_USER}"] = array('write', 'read');
				} else {
					// TODO: trigger error $record->L_APPOITMENT_USER is not active user!
				}
			}

			if (!empty($record->A_ADM_COMPANY) && $record->A_ADM_COMPANY == $record->A_CLASSIFIED) {
				$requirePermsExt["group:{$record->A_ADM_COMPANY}"] = array('read', 'write');
			}
			else if (empty($record->A_ADM_COMPANY) && empty($record->A_CLASSIFIED)) {
				$requirePermsExt["group:workgroup"] = array('write', 'read');
			}
			else {
				if ($record->A_ADM_COMPANY) {
					$requirePermsExt["group:{$record->A_ADM_COMPANY}"] = array('write');
				}
				if ($record->A_CLASSIFIED) {
					$requirePermsExt["group:{$record->A_CLASSIFIED}"] = array('read');
				}
			}
		}

		// add parent owner
		if (!empty($record->PARENT_L_APPOITMENT_USER) && $record->PARENT_L_APPOITMENT_USER != $record->L_APPOITMENT_USER) {
			if ($this->_isActiveUser($record->PARENT_L_APPOITMENT_USER)) {
				$requirePermsExt["user:{$record->PARENT_L_APPOITMENT_USER}"] = array('write', 'read');
			} else {
				// TODO: trigger error $record->PARENT_L_APPOITMENT_USER is not active user!
			}
		}

		// add user:_www perms read, write
		$requirePermsExt["user:_www"] = array('write', 'read');

		return $requirePermsExt;
	}

	private function _fixFilePerms($vFile, $requirePermsExt, $mainPath, $r) {
		//trigger_error("fixFilePerms: " . json_encode($vFile), E_USER_NOTICE);
		$cmdList = array();
		$vReqPermsExt = array();
		foreach ($requirePermsExt as $kPermObj => $vPerms) {
			$vReqPermsExt[$kPermObj] = array();
			foreach ($vPerms as $vPerm) {
				if ($vFile->isDir) {
					if ($vPerm == 'read') {
						$vReqPermsExt[$kPermObj]['list'] = 0;
						$vReqPermsExt[$kPermObj]['search'] = 0;
						$vReqPermsExt[$kPermObj]['readattr'] = 0;
						$vReqPermsExt[$kPermObj]['readextattr'] = 0;
						$vReqPermsExt[$kPermObj]['readsecurity'] = 0;
						$vReqPermsExt[$kPermObj]['file_inherit'] = 0;
						$vReqPermsExt[$kPermObj]['directory_inherit'] = 0;
					}
					else if ($vPerm == 'write') {
						$vReqPermsExt[$kPermObj]['add_file'] = 0;
						$vReqPermsExt[$kPermObj]['add_subdirectory'] = 0;
						$vReqPermsExt[$kPermObj]['delete_child'] = 0;
						$vReqPermsExt[$kPermObj]['writeattr'] = 0;
						$vReqPermsExt[$kPermObj]['writeextattr'] = 0;
						$vReqPermsExt[$kPermObj]['writesecurity'] = 0;
						$vReqPermsExt[$kPermObj]['chown'] = 0;
					}
				}
				else {
					if ($vPerm == 'read') {
						$vReqPermsExt[$kPermObj]['read'] = 0;
						$vReqPermsExt[$kPermObj]['execute'] = 0;
						$vReqPermsExt[$kPermObj]['readattr'] = 0;
						$vReqPermsExt[$kPermObj]['readextattr'] = 0;
						$vReqPermsExt[$kPermObj]['readsecurity'] = 0;
					}
					else if ($vPerm == 'write') {
						$vReqPermsExt[$kPermObj]['write'] = 0;
						$vReqPermsExt[$kPermObj]['append'] = 0;
						$vReqPermsExt[$kPermObj]['writeattr'] = 0;
						$vReqPermsExt[$kPermObj]['writeextattr'] = 0;
						$vReqPermsExt[$kPermObj]['writesecurity'] = 0;
						$vReqPermsExt[$kPermObj]['chown'] = 0;
					}
				}
			}
		}

		$permsToRemove = array();
		foreach ($vFile->permsExt as $vPermExtLine) {
			$vPermExtArr = explode(' ', $vPermExtLine);
			$vPermExt = new stdClass();
			$vPermExt->nr = trim($vPermExtArr[0], ' :');
			$vPermExt->permObj = $vPermExtArr[1];
			$vPermExt->perms = '';
			if (count($vPermExtArr) == 4) {
				$vPermExt->perms = explode(',', $vPermExtArr[3]);
			}
			else if (count($vPermExtArr) == 5 && $vPermExtArr[2] == 'inherited') {
				$vPermExt->perms = explode(',', $vPermExtArr[4]);
			}
			else {
				echo "Error: Perm Ext Line format! ({$vPermExtLine})";
				continue;
			}

			$permFound = false;
			foreach ($vReqPermsExt as $kPermObj => $vPerms) {
				if (false !== strpos($vPermExtLine, $kPermObj)) {
					foreach ($vPerms as $kPerm => $vPermChecked) {
						if (in_array($kPerm, $vPermExt->perms)) {
							$vReqPermsExt[$kPermObj][$kPerm] = 1;
							$permFound = true;
						}
					}
				}
			}
			if (!$permFound) {
				$permsToRemove[] = $vPermExt->nr;
			}
			//echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$vReqPermsExt('.$vFile->name.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vReqPermsExt);echo'</pre>';
		}

		if (!empty($permsToRemove)) {
			if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:300px;overflow:auto;border:1px solid red;text-align:left;">TO-rm('.$r->ID.'): $permsToRemove('.$vFile->name.') isDir('.$vFile->isDir.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('permsToRemove'=>$permsToRemove, 'vFile->permsExt'=>$vFile->permsExt, 'r'=>$r));echo'</pre>';}
			$permsToRemove = array_reverse($permsToRemove);
			foreach ($permsToRemove as $vPermInd) {
				$cmdList []= "chmod -a# {$vPermInd} '{$mainPath}/{$vFile->path}' ";
			}
		}

		$permsToAdd = array();
		foreach ($vReqPermsExt as $kPermObj => $vPerms) {
			foreach ($vPerms as $kPerm => $vPermChecked) {
				if (!$vPermChecked) {
					$permsToAdd[$kPermObj][$kPerm] = 1;
				}
			}
		}
		if (!empty($permsToAdd)) {
			if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:300px;overflow:auto;border:1px solid red;text-align:left;">TO-add('.$r->ID.'): $permsToAdd('.$vFile->name.') isDir('.$vFile->isDir.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('permsToAdd'=>$permsToAdd, 'vFile->permsExt'=>$vFile->permsExt, 'r'=>$r));echo'</pre>';}
			foreach ($permsToAdd as $kPermObj => $vPerms) {
				$cmdList []= "chmod +a \"{$kPermObj} allow " . implode(',', array_keys($vPerms)) . "\" '{$mainPath}/{$vFile->path}' ";
			}
		}
		return $cmdList;
	}

	private function _isActiveUser($usrLogin) {
		$activeLoginList = $this->_fetchActiveLoginList();
		return in_array($usrLogin, $activeLoginList);
	}

	public function _fetchActiveLoginList() {
		if (!is_array($this->_activeLoginList)) {
			$this->_activeLoginList = array();
			$sql = "
				select u.`ADM_ACCOUNT`
				from `ADMIN_USERS` as u
				where u.`A_STATUS` =  'NORMAL'
			";
			$this->_activeLoginList = DB::getPDO()->fetchValuesList($sql);
		}
		return $this->_activeLoginList;
	}

	private function _saveTask($tblName, $tblId, $taskDone = 1, $result = null) {
		$sqlResult = ($result)
		?	" , s.`A_SYNC_RESULT` = " . DB::getPDO()->quote( json_encode($result) )
		:	'';
		$sql = "
			update `{$this->_syncTblName}` as s
			set
				s.`A_LAST_SYNC`=NOW()
				, s.`A_SYNCHRONIZED`={$taskDone}
				{$sqlResult}
			where s.`TBL_ID`={$tblId} and s.`TBL_NAME`='{$tblName}'
		";
		DB::getPDO()->execSql($sql);
	}

	public function executeTask($tblName, $tblId) {
		if (!$this->_tbls) return;
		$task = $this->_getTask($tblName, $tblId);
		if (!$task) return;

		$this->_executeTask($task);
		$this->_saveTask($task->TBL_NAME, $task->TBL_ID);
		return $task;
	}

	public function nextTaskAction($tblName) {
		if (!$this->_tbls) return;

		$task = $this->_getTask($tblName);
		if (!$task) return;

		$this->_executeTask($task);

		/*
		if (isset($task->A_SYNC_RESULT->total)
			&& isset($task->A_SYNC_RESULT->lastDone)
			&& $task->A_SYNC_RESULT->lastDone < $task->A_SYNC_RESULT->total
		) {
			$this->_saveTask($task->TBL_NAME, $task->TBL_ID, 0, $task->A_SYNC_RESULT);
		} else {
			$this->_saveTask($task->TBL_NAME, $task->TBL_ID);
		}
		*/
		$this->_saveTask($task->TBL_NAME, $task->TBL_ID);
		return $task;
	}

	private function _getTask($tblName, $tblId = null) {
		$task = null;

		$recordFieldList = $this->_getRecordFieldList($tblName);
		$recordFieldList[] = 'L_APPOITMENT_USER';
		$recordFieldList[] = 'A_ADM_COMPANY';
		$recordFieldList[] = 'A_CLASSIFIED';

		$sqlRecordFields = array();
		foreach ($recordFieldList as $fldName) {
			$sqlRecordFields[] = "t.`{$fldName}` as t__{$fldName}";
		}
		$sqlRecordFields = implode(',', $sqlRecordFields);

		$sqlTblId = "and s.`TBL_ID`!=-1";
		if ($tblId > 0) $sqlTblId = "and s.`TBL_ID`='{$tblId}'";

		$sql = "
			select s.*
				, {$sqlRecordFields}
		--		, pt.`L_APPOITMENT_USER` as p__PARENT_L_APPOITMENT_USER
			from `{$this->_syncTblName}` as s
				left join `{$tblName}` as t on(t.`ID`=s.`TBL_ID`)
		--		left join `{$tblName}` as pt on(pt.`ID`=t.`P_ID`)
			where
				s.`TBL_NAME`='{$tblName}'
				{$sqlTblId}
				and s.`A_SYNCHRONIZED`=0
				and s.`A_POMIN_SYNC`=0
			order by s.`A_LAST_SYNC` asc, s.`ID` asc
			limit 1
		";
		$row = DB::getPDO()->fetchFirst($sql);
		if ($row) {
			$r = (object)$row;
			$r->A_SYNC_RESULT = (!$r->A_SYNC_RESULT)? new stdClass() : json_decode($r->A_SYNC_RESULT);
			$task = $r;
			$task->record = new stdClass();
			foreach ($recordFieldList as $fldName) {
				$aliasFldName = "t__{$fldName}";
				$task->record->{$fldName} = $task->{$aliasFldName};
				unset($task->{$aliasFldName});
			}
		}
		return $task;
	}

	private function _getRecordFieldList($tblName) {
		if (empty($this->_tbls[$tblName])) return;
		$conf = $this->_tbls[$tblName];

		$fldNames = array();

		$fldName = V::get("LOOKAT_FOLDER_VARNAME", '', $conf);
		$fldName = trim($fldName);
		if (!empty($fldName)) $fldNames[$fldName] = true;

		for ($i = 1; $i < 5; $i++) {
			$fldName = V::get("LOOKAT_FOLDER_VARNAME{$i}", '', $conf);
			$fldName = trim($fldName);
			if (!empty($fldName)) $fldNames[$fldName] = true;

			$fldName = V::get("DEST_FOLDER_VARNAME{$i}", '', $conf);
			$fldName = trim($fldName);
			if (!empty($fldName)) $fldNames[$fldName] = true;
		}

		if (!in_array('ID', $fldNames)) {
			$fldNames['ID'] = true;
		}
		return array_keys($fldNames);
	}

	private function _executeTask($task) {
		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">executeTask (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($task);echo'</pre>';}

		$DBG_TIME = false;
		$dbgTime = new stdClass();
		$dbgTime->startTime = microtime(true);
		$dbgTime->lastTime = $dbgTime->startTime;

		$confTblName = "{$task->TBL_NAME}_COLUMN";
		$TBL_mount_point = FoldersConfig::get($confTblName, 'mount_point');

		$record = $task->record;

		$folderConf = FoldersConfig::getAll($confTblName);
		$uploader = new FileUploader($confTblName, $record);
		$errMsg = '';
		if (!$uploader->setConfig($folderConf, $errMsg)) {
			trigger_error("Error: folders config!", E_USER_NOTICE);
			die("Error: folders config!");
		}

		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):" . __LINE__ . ":_executeTask({$task->TBL_NAME}/{$task->TBL_ID}]: after-FileUploader->setConfig" . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		$uploader->findFolder();
		$mainFolder = $uploader->getDestFolder();

		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):" . __LINE__ . ":_executeTask({$task->TBL_NAME}/{$task->TBL_ID}]:time after-FileUploader->getDestFolder" . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">generated folderName('.$record->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';var_dump($uploader);echo'</pre>';}
		if (!$mainFolder) {
			$mainFolder = $uploader->generateFolderName();
			$mainFolderPath = rtrim($TBL_mount_point, '/') . '/' . $mainFolder;
			if(V::get('DBG', '', $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">generated folderName('.$record->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($mainFolder, $mainFolderPath));echo'</pre>';}

			@mkdir($mainFolderPath, 0770, true);
			@chmod($mainFolderPath, 0770);
			if (!file_exists($mainFolderPath)) {
				trigger_error("Error: Nie udało się utworzyć folderu! ({$mainFolder})", E_USER_NOTICE);
				return false;
			}
			$requirePermsExt = $this->_getRequiredPermsExtByRecord($record);
			$file = new stdClass();
			$file->name = '.';
			$file->path = '.';
			//$file->owner = '';
			//$file->group = '';
			//$file->perms = '';
			$file->isDir = true;
			$file->permsExt = array();
			$cmdList = $this->_fixFilePerms($file, $requirePermsExt, "{$TBL_mount_point}/{$mainFolder}", $record);
			if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmdList-P('.$record->ID.') fixFilePerms (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdList);echo'</pre>';}
		}
		else {
			$cmdList = $this->_fixRecord($record, $uploader, $mainFolder, $TBL_mount_point, $task);
		}

		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):" . __LINE__ . ":_executeTask({$task->TBL_NAME}/{$task->TBL_ID}]: after-generate cmdList - mainFolder(" . (($mainFolder)? 1 : 0) . ")" . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		if (!empty($cmdList)) {
			foreach ($cmdList as $vCmd) {
				if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Exec-cmdList-P('.$record->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($vCmd);echo'</pre>';}

				exec($vCmd, $out, $outValue);

				if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Done-cmdList-P('.$record->ID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array($outValue, $out));echo'</pre>';}
				if ($outValue != 0) {
					trigger_error("Error-cmd: {$vCmd}", E_USER_NOTICE);
				}
			}
		}

		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):" . __LINE__ . ":_executeTask({$task->TBL_NAME}/{$task->TBL_ID}]: after-executed cmdList " . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		if(V::get('DBG', '', $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">';print_r("executeTask finished OK");echo'</pre>';}
	}

	public function _fixRecord($r, $uploader, $mainFolder, $TBL_mount_point, &$task) {
		$requirePermsExt = $this->_getRequiredPermsExtByRecord($r);

		$DBG_TIME = false;
		$dbgTime = new stdClass();
		$dbgTime->startTime = microtime(true);
		$dbgTime->lastTime = $dbgTime->startTime;

		$filesWithPerms = array();
		if ($uploader->hasRecordOwnFolder()) {
			$cmd = " ls -Rlea . ";
			$cmd = "cd '{$TBL_mount_point}/{$mainFolder}' && {$cmd} ";
			exec($cmd, $filesWithPerms);
		}
		else {
			$lookGlob = $uploader->getFileSearchRegex();
			$cmd = " ls -Rlea {$lookGlob} ";
			$cmd = "cd '{$TBL_mount_point}/{$mainFolder}' && {$cmd} ";
			exec($cmd, $filesWithPerms);
		}

		//echo "\n-----------------------\n";
		//echo "filesWithPerms: ";print_r($filesWithPerms);echo "\n";
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):_fixRecord: cd '{$TBL_mount_point}/{$mainFolder}' && {$cmd} " . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		$filesWithPermsTree = array();
		$lastName = '';
		$folderParent = '';
		foreach ($filesWithPerms as $line) {
			if (substr($line, 0, 1) == '-' || substr($line, 0, 1) == 'd') {// file name
				$fileParts = array();
				$filePartsArr = explode(' ', $line);
				$lastInd = 0;
				foreach ($filePartsArr as $vPart) {
					if ($lastInd > 7) {// file name
						$fileParts[8] = ($lastInd > 8)? "{$fileParts[8]} {$vPart}" : $vPart;
						$lastInd++;
					}
					else if (!empty($vPart) || $vPart === '0') {
						$fileParts[] = $vPart;
						$lastInd++;
					}
				}

				if (end($fileParts) == '..' || (end($fileParts) == '.' && $folderParent)) {
					$lastName = '..';
					continue;
				}

				$file = new stdClass();

				if (!isset($fileParts[8])) {
					echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Error parse file name (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r(array('line'=>$line, 'fileParts'=>$fileParts));echo'</pre>';
					//continue;
				}

				$file->name = end($fileParts);
				$file->path = array();
				if ($folderParent) $file->path[] = $folderParent;
				$file->path[] = end($fileParts);
				$file->path = implode('/', $file->path);
				//$file->owner = $fileParts[2];
				//$file->group = $fileParts[3];
				//$file->perms = $fileParts[0];
				$file->isDir = (substr($line, 0, 1) == 'd');
				$file->permsExt = array();
				$filesWithPermsTree[$file->name] = $file;
				$lastName = $file;
			}
			else if (substr($line, 0, 1) == ' ') {// extended perm line
				if ($lastName == '..') {
					continue;
				}
				$filesWithPermsTree[$file->name]->permsExt[] = trim($line);
			}
			else if (substr($line, 0, 2) == './' && substr($line, -1) == ':') {// folder
				$folderParent = substr($line, 2, -1);
			}
			else if (substr($line, 0, 5) == 'total') {
				continue;
			}
			else if (empty($line)) {
				$folderParent = '';
				continue;
			}
		}

		//echo "filesWithPermsTree: ";print_r($filesWithPermsTree);echo "\n";
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):_fixRecord: parse cmd output" . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		if(V::get('DBG', '', $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$filesWithPermsTree (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($filesWithPermsTree);echo'</pre>';}

		// check perms
		/*
files:
W		read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown
W		read,write,execute,append,readattr,writeattr,readextattr,writeextattr,readsecurity
R		read,      execute,       readattr,          readextattr,             readsecurity

dir:
W		list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,writesecurity,chown,file_inherit,directory_inherit
W		list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,                    file_inherit,directory_inherit
R		list,         search,                              readattr,          readextattr,             readsecurity,                    file_inherit,directory_inherit
		 */
		$cmdList = array();
		foreach ($filesWithPermsTree as $kFileName => $vFile) {
			if (false) {
				// TODO: $kFileName == '_PISMA' - readonly
				// TODO: $vFile->path => _PISMA/...
			}
			else {
				$cmdFile = $this->_fixFilePerms($vFile, $requirePermsExt, "{$TBL_mount_point}/{$mainFolder}", $r);
				if (!empty($cmdFile)) {
					foreach ($cmdFile as $vCmd) {
						$cmdList[] = $vCmd;
					}
				}
			}
		}
		//echo "cmdList: ";print_r($cmdList);echo "\n";
		//echo "\n-----------------------\n";
		if ($DBG_TIME) {
			$dbgTime->curTime = microtime(true);
			echo "DBG:(" . number_format($dbgTime->curTime - $dbgTime->lastTime, 6) . "s):_fixRecord: end" . "\n";
			$dbgTime->lastTime = $dbgTime->curTime;
		}

		return $cmdList;
	}

}

?>
