<?php

/**
 * Proces Log Edit form
 * 
 * State saved in session @see save_state, read_state
 * 
 * _komorki - ID jest dodawana automatycznie
 * TODO: ID moze byc tez we wskaznikach
 * TODO: w _komorki jest tylko ID ze wskaznikow - show Error - blednie zdefiniowane wskazniki
 */
class ProcesLogEdit {

	private $_log_id;// Log ID
	private $_log_wsk_id;// Log wsk ID
	private $_tbl_zasob_id;
	private $_komorki;// array of wskazniki (typu KOMORKA)
	private $_v;// array of form field values
	private $_filters;// array of table filter values
	private $_filters_readonly;// array of readonly filters
	private $_nav_filters;// array of table filter values
	private $_nav_filters_defaults;// array of table filter default values
	private $_errors;// array of errors in form
	private $_id_key;// Record ID
	private $_last_id_key;// ostatnio edytowany Record ID - value from DB, must be set after create object

	function __construct($log_id, $log_wsk_id) {
		$this->_log_id = $log_id;
		$this->_log_wsk_id = $log_wsk_id;
		$this->_tbl_zasob_id = 0;
		$this->_komorki = array();
		$this->_v = array();
		$this->_errors = array();
		$this->_filters = array();
		$this->_filters_readonly = array();
		$this->_nav_filters = array();
		$this->_nav_filters_defaults = array();// name => default
		$this->_nav_filters_defaults ['f-_order_by'] = 'ID';
		$this->_nav_filters_defaults ['f-_order_desc'] = 'DESC';
		$this->_nav_filters_defaults ['f-_offset'] = '';
		$this->_nav_filters_defaults ['f-_limit'] = '10';

		$this->_id_key = 0;
		$this->_last_id_key = 0;

		$this->read_state();
	}

	function __destruct() {
		$this->save_state();
	}

	/**
	 * Set current edit row data.
	 * Only first time - used by form set value.
	 */
	function set_row($row) {
		if ($this->get('ID') == $row->f_ID) {
			return;// only if not in cache
		}
		$row_fields = get_object_vars($row);
		foreach ($row_fields as $k => $v) {
			$k_field = (substr($k, 0, 2) == 'f_')? substr($k, 2) : $k;
			$this->set($k_field, $v);
		}
	}

	/**
	 * Clear current edit row data.
	 */
	function clear_row() {
		$this->_v = array();
	}

	function set($k_wsk_id, $val) {
		if (substr($k_wsk_id, 0, 2) == 'v-') {
			$k_wsk_id = substr($k_wsk_id, 2);
		}
		if ($k_wsk_id == 'ID' || array_key_exists($k_wsk_id, $this->_komorki)) {
			$this->_v[$k_wsk_id] = $val;
		}
	}

	function get($k_wsk_id) {
		if (substr($k_wsk_id, 0, 2) == 'v-') {
			$k_wsk_id = substr($k_wsk_id, 2);
		}
		if (array_key_exists($k_wsk_id, $this->_v)) {
			return $this->_v[$k_wsk_id];
		}
		return null;
	}

	/**
	 * Set edit values from request or from given args.
	 * @param $args - array of args - $_POST, $_GET or another array, if not set use $_REQUEST
	 */
	public function set_values_from_request($_args = array()) {
		if (empty($_args)) {
			$_args = $_REQUEST;
		}
		foreach ($this->get_komorki() as $k_wsk_id => $v_cell) {
			if (isset($_args['v-' . $k_wsk_id])) {
				$this->set($k_wsk_id, V::get('v-' . $k_wsk_id, '%', $_args));
			}
		}
	}

	/**
	 * Set filters from request or from given args.
	 * @param $args - array of args - $_POST, $_GET or another array, if not set use $_REQUEST
	 */
	public function set_filters_from_request($_args = array()) {
		if (empty($_args)) {
			$_args = $_REQUEST;
		}
		$this->set_filter('ID', V::get('f-ID', '%', $_args));
		foreach ($this->get_komorki() as $k_wsk_id => $v_cell) {
			if (isset($_args['f-' . $k_wsk_id])) {
				$this->set_filter($k_wsk_id, V::get('f-' . $k_wsk_id, '%', $_args));
			}
		}

		foreach ($this->_nav_filters_defaults as $k_fltr => $v_default) {
			if (isset($_args[$k_fltr])) {
				$this->set_nav_filter($k_fltr, $_args[$k_fltr]);
			}
		}
	}

	public function set_filter($k_wsk_id, $val) {
		if (substr($k_wsk_id, 0, 2) == 'f-') $k_wsk_id = substr($k_wsk_id, 2);
		if (array_key_exists($k_wsk_id, $this->_filters_readonly)) {
			return;// prevent for change readonly values
		}
		if ($val == '%') {
			unset($this->_filters[$k_wsk_id]);
		}
		if ($k_wsk_id == 'ID' || array_key_exists($k_wsk_id, $this->_komorki)) {
			$this->_filters[$k_wsk_id] = $val;
		}
	}

	/**
	 * Get filter value - default '%'.
	 */
	function get_filter($k_wsk_id) {
		if (substr($k_wsk_id, 0, 2) == 'f-') $k_wsk_id = substr($k_wsk_id, 2);
		if (array_key_exists($k_wsk_id, $this->_filters_readonly)) {
			return $this->_filters_readonly[$k_wsk_id];// TODO: evaluate by values in _HIST
		}
		if (array_key_exists($k_wsk_id, $this->_filters)) {
			return $this->_filters[$k_wsk_id];
		}
		return '%';
	}

	function set_nav_filter($fltr, $value) {
		if (array_key_exists($fltr, $this->_nav_filters_defaults)) {
			$this->_nav_filters[$fltr] = $value;
		}
	}

	function get_nav_filter($fltr) {
		return (array_key_exists($fltr, $this->_nav_filters_defaults))? V::get($fltr, $this->_nav_filters_defaults[$fltr], $this->_nav_filters) : '';
	}

	function add_error($msg) {
		$this->_errors[] = $msg;
	}

	function has_errors() {
		return !empty($this->_errors);
	}

	function get_errors() {
		return $this->_errors;
	}

	function get_log_id() {
		return $this->_log_id;
	}

	function get_log_wsk_id() {
		return $this->_log_wsk_id;
	}

	function set_tbl_zasob_id($tbl_zasob_id) {
		$this->_tbl_zasob_id = $tbl_zasob_id;
	}

	function get_tbl_zasob_id() {
		return $this->_tbl_zasob_id;
	}

	function add_komorka($wsk) {
		$this->_komorki[$wsk->CW_ID] = $wsk;
		// TODO: add $this->_filters
		//$this->_filters[$wsk->] = $wsk-> value from field ... - TODO: evaluate variable from _HIST
		// TODO: add $this->_filters_readonly
		//$this->_filters_readonly[$wsk->] = $wsk-> value from field ... - TODO: evaluate variable from _HIST
	}

	public function get_wsk_ids() {
		return array_keys($this->_komorki);
	}

	function get_komorki() {
		return $this->_komorki;
	}

	function set_id_key($id_key) {
		$this->_id_key = $id_key;
	}

	function get_id_key() {
		return $this->_id_key;
	}

	function set_last_id_key($last_id_key) {
		$this->_last_id_key = $last_id_key;
	}

	function get_last_id_key() {
		return $this->_last_id_key;
	}

	/**
	 * Set type of form: read, edit, create, search, note
	 *   edit - record ID, contains 'W' in CP_FORM_TREAT
	 *   create - contains 'C' in CP_FORM_TREAT
	 *   search - table
	 *   note - use notes table (TODO: CRM_LOG_NOTES(id, log_id, wsk_id, value))
	 * TODO: check if type is allowed before set
	 */
	function set_type($type) {
		switch ($type) {
			case 'create':
				if ($this->allow_create()) {
					$this->_type = $type;
					$this->clear_row();// clear $this->_v
				}
				break;
			case 'edit':
				if ($this->allow_write()) {
					$this->_type = $type;
					$this->clear_row();// clear $this->_v
				}
				break;
			case 'read':
				if ($this->allow_read()) {
					$this->_type = $type;
					$this->clear_row();// clear $this->_v
				}
				break;
			default:
				$this->_type = $type;
		}
	}

	/**
	 * Get type of form: read, edit, create, search, note
	 */
	function get_type() {
		if ($this->_type != '') {
			return $this->_type;
		}
		if ($this->_id_key > 0) {
			return 'edit';
		}
	}

	function add_hidden_field($k_field, $v_value) {
		$this->_hidden_fields[$k_field] = $v_value;
	}

	function render_hidden_fields() {
		$out = '';
		foreach ($this->_hidden_fields as $k_field => $v_value) {
			$out .= '<input type="hidden" name="' . $k_field . '" value="' . $v_value . '" />';
		}
		$out .= '<input type="hidden" name="' . "_log_id" . '" value="' . $this->get_log_id() . '" />';
		$out .= '<input type="hidden" name="' . "_tbl_zasob_id" . '" value="' . $this->_tbl_zasob_id . '" />';
		$out .= '<input type="hidden" name="' . "_log_wsk_id" . '" value="' . $this->_log_wsk_id . '" />';
		$out .= '<input type="hidden" name="' . "_type" . '" value="' . $this->get_type() . '" />';
		return $out;
	}

	/**
	 * TODO: View form (edit, search, add new, add note).
	 * Always show table with search filters. Add ID column if not set.
	 * Row edit action sets $this->_id_key and change view to edit form.
	 * TODO: change reauests to AJAX.
	 */
	function form() {
		$out = '';
		$out .= '<form action="" method="post">';
		$out .= $this->render_hidden_fields();
		switch ($this->get_type()) {
			case 'edit':
				$out .= $this->render_edit();
				break;
			case 'read':
				$out .= $this->render_read();
				break;
			case 'create':
				$out .= $this->render_create();
				break;
			default:
				$out .= $this->render_table();
		}
		$out .= '</form>';
		return $out;
	}

	function render_create() {
		$out = '';
		$out .= '<table cellspacing="0" cellpadding="0" border="1" class="tbl-view">';
		$out .= '<thead>';
		$out .= '<tr><td>';
		$out .= '<p>' . "Nowy rekord do tabeli " . $this->get_table_name() . '</p>';
		$out .= '</td></tr>';
		$out .= '</thead>';
		$out .= '<tbody>';
		$t = 0;
		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$k_field = 'v-' . $k_wsk_id;// TODO: $v_wsk->CW_ID - ID wskaznik
			$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
			if ($v_wsk->OPIS) {
				$out .= $v_wsk->OPIS . ' ';
			}
			if ($v_wsk->OPIS_ZASOB) {
				$out .= $v_wsk->OPIS_ZASOB . ' ';
			}
			$out .= '<br />';
			$out .= '<label for="' . $k_field . '">' . $v_wsk->DESC . ': ' . '</label>';
			$out .= ' <em style="color:#666">(' . $v_wsk->CP_FORM_TREAT . ')</em> ';
			// $v_wsk->CP_FORM_TREAT => R,W,X,C
			// TODO: use App::field();
			$perms = explode(',', $v_wsk->CP_FORM_TREAT);
			if (in_array('C', $perms)) {
				$sql_type = $this->get_field_type($k_wsk_id);
				if (is_object($sql_type)) {
					$out .= App::field($k_field, $sql_type->Type, $this->get($k_wsk_id));
				} else {
					$out .= '<input type="text" name="' . $k_field . '" value="' . $this->get($k_wsk_id) . '" />';
				}
			} else if (in_array('R', $perms)) {
				$out .= '"' . $this->get($k_wsk_id) . '"';
			} else {
				$out .= '<span class="red">' . "brak dostępu" . '</span>';
			}
			$out .= '</td></tr>';
		}
		$out .= '</tbody>';
		$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
		$out .= '<input type="submit" value="' . "Zapisz" . '" />';
		if ($this->allow_read()) {
			$out .= " lub ";
			$js = "this.form['_action'].value='edit_back';";
			$out .= '<input type="submit" onclick="' . $js . '" value="' . "Wróć do wyszukiwania" . '" />';
		}
		$out .= '</td></tr>';
		$out .= '</table>';
		return $out;
	}

	function render_edit() {
		$out = '';

		$t = 0;
		$out .= '<table cellspacing="0" cellpadding="0" border="1" class="tbl-view">';
		$row = $this->fetch_row();
		if (!$row) {
			$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
			$out .= '<p>' . "Brak danych!" . '</p>';
			$out .= '</td></tr>';
			// TODO: check if _id_key isset - if yes show back link witch clear arg _id_key
			// optional show error message why cant edit this _id_key
			$out .= '</table>';
			return $out;
		}
		$this->set_row($row);

		$out .= '<thead>';
		$out .= '<tr><td>';
		$out .= '<input type="hidden" name="' . "_action" . '" value="' . "save_edit" . '" />';
		$out .= '<p>' . '<label for="f_ID">' . "Rekord ID: " . '</label>' . '<b>' . $row->f_ID . '</b>' . '</p>';
		$out .= '<input type="hidden" name="' . "_id_key" . '" value="' . $row->f_ID . '" />';
		$out .= '</td></tr>';
		$out .= '</thead>';
		$out .= '<tbody>';
		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
			$wsk_id = (substr($k_wsk_id, 0, 2) == 'f-')? substr($k_wsk_id, 2) : $k_wsk_id;
			$k_field = 'v-' . $wsk_id;
			$out .= '<label for="' . $k_field . '">' . $v_wsk->DESC . ': ' . '</label>';
			// TODO: $v_wsk->OPIS - opis z zasobu
			// TODO: $v_wsk->OPIS_ZASOB - opis ze wskaznika
			// TODO: use App::field();
			$out .= '<input type="text" name="' . $k_field . '" value="' . $this->get($k_wsk_id) . '" />';
			// TODO: $v_wsk->CW_ID - ID wskaznik
			// TODO: $v_wsk->CP_FORM_TREAT => R,W,X,C
			$out .= '<span style="display:none">' . "[" . $v_wsk->CP_FORM_TREAT . " / " . $v_wsk->CW_ID . "]" . '</span>';
			$out .= '</td></tr>';
		}
		$out .= '</tbody>';
		$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
		$out .= '<input type="submit" value="' . "Zapisz" . '" />';
// TODO: if allow_read
		$out .= " lub ";
		$js = "this.form['_action'].value='edit_back';";
		$out .= '<input type="submit" onclick="' . $js . '" value="' . "Wróć do wyszukiwania" . '" />';
		$out .= '</td></tr>';
		$out .= '</table>';
		return $out;
	}

	function render_read() {
		$out = '';

		$t = 0;
		$out .= '<table cellspacing="0" cellpadding="0" border="1" class="tbl-view">';
		$row = $this->fetch_row();
		if (!$row) {
			$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
			$out .= '<p>' . "Brak danych!" . '</p>';
			$out .= '</td></tr>';
			// TODO: check if _id_key isset - if yes show back link witch clear arg _id_key
			// optional show error message why cant edit this _id_key
			$out .= '</table>';
			return $out;
		}
		$this->set_row($row);

		$out .= '<thead>';
		$out .= '<tr><td>';
		$out .= '<input type="hidden" name="' . "_action" . '" value="' . "save_edit" . '" />';
		$out .= '<p>' . '<label for="f_ID">' . "Rekord ID: " . '</label>' . '<b>' . $row->f_ID . '</b>' . '</p>';
		$out .= '<input type="hidden" name="' . "_id_key" . '" value="' . $row->f_ID . '" />';
		$out .= '</td></tr>';
		$out .= '</thead>';
		$out .= '<tbody>';
		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
			$wsk_id = (substr($k_wsk_id, 0, 2) == 'f-')? substr($k_wsk_id, 2) : $k_wsk_id;
			$k_field = 'v-' . $wsk_id;
			$out .= '<label for="' . $k_field . '">' . $v_wsk->DESC . ': ' . '</label>';
			// TODO: $v_wsk->OPIS - opis z zasobu
			// TODO: $v_wsk->OPIS_ZASOB - opis ze wskaznika
			// TODO: use App::field();
			$out .= '<input type="text" name="' . $k_field . '" value="' . $this->get($k_wsk_id) . '" readonly="readonly" />';
			// TODO: $v_wsk->CW_ID - ID wskaznik
			// TODO: $v_wsk->CP_FORM_TREAT => R,W,X,C
			$out .= '<span style="display:none">' . "[" . $v_wsk->CP_FORM_TREAT . " / " . $v_wsk->CW_ID . "]" . '</span>';
			$out .= '</td></tr>';
		}
		$out .= '</tbody>';
		$out .= '<tr class="row-' . ($t = 1 - $t) . '"><td>';
		$js = "this.form['_action'].value='edit_back';";
		$out .= '<input type="submit" onclick="' . $js . '" value="' . "Wróć do wyszukiwania" . '" />';
		$out .= '</td></tr>';
		$out .= '</table>';
		return $out;
	}

	function render_table() {
		$out = '';
		$cols = array();

		$cols['ID'] = array();
		$cols['ID']['ID'] = 'ID';
		$cols['ID']['DESC'] = 'ID';
		$cols['ID']['PERM'] = 'R';// TODO: check by PERMS in other fields
		$cols['ID']['OPIS'] = '';
		$cols['ID']['OPIS_ZASOB'] = '';

		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$cols[$k_wsk_id] = array();
			$cols[$k_wsk_id]['ID'] = $v_wsk->ID;
			$cols[$k_wsk_id]['DESC'] = $v_wsk->DESC;
			$cols[$k_wsk_id]['PERM'] = $v_wsk->CP_FORM_TREAT;
			$cols[$k_wsk_id]['OPIS'] = $v_wsk->OPIS;
			$cols[$k_wsk_id]['OPIS_ZASOB'] = $v_wsk->OPIS_ZASOB;
		}

		$rows = array();
		$rows_total = $this->fetch_rows_total();
		if ($rows_total > 0) {
			$rows = $this->fetch_rows();
		}

		$f_offset = V::get('f-_offset', '', $this->_nav_filters);
		Lib::loadClass('PageNav');
		$nav = new PageNav($rows_total, $f_offset);
		$nav->set_request_args(array('task'=>'PROCES_LOG', '_log_id'=>$this->get_log_id()));
		$nav->set_request_method_post();
		$nav->set_request_arg_page_nr_name('f-_offset');

		if ($this->_last_id_key) {
			$out .= '<p>' . "Ostatnio wybrany/edytowany rekord: " . '<b>' . $this->_last_id_key . '</b>' . '</p>';
		}

		$filters_set = false;
		foreach ($this->_filters as $k => $v) {
			// TODO: tylko filtry ktore moze ustawic user (wskaznik tylko R do danego filtra)
			if ($v != '%') {
				$filters_set = true;
			}
		}

		foreach ($this->_nav_filters_defaults as $k_fltr => $v_default) {
			$out .= '<input type="hidden" name="' . $k_fltr . '" value="' . $this->get_nav_filter($k_fltr) . '" />';
		}

		$out .= '<input type="hidden" name="' . "_action" . '" value="" />';
		$out .= '<input type="hidden" name="' . "_id_key" . '" value="" />';

		$out .= '<table cellspacing="0" cellpadding="0" border="1" class="tbl-view">';
		$out .= '<thead>';
		$out .= '<tr class="tbl-head">';
			$out .= '<th>';
				if ($this->allow_create()) {
					// change state to add new record
					$js = "this.form['_action'].value='create';this.form['_id_key'].value='';";
					$out .= " " . '<input type="image" onclick="' . $js . '" src="icon/add.gif" alt="add" title="' . "Dodaj nowy rekord" . '">';
				} else {
					$out .= '...';// header for actions
				}
			$out .= '</th>';
			foreach ($cols as $k_wsk_id => $v_field) {
				$out .= '<th>';
					$out .= str_replace('_' ,' ', $v_field['DESC']);
					if ($k_wsk_id == $this->get_nav_filter('f-_order_by')) {
						if ('asc' == $this->get_nav_filter('f-_order_desc')) {
							$js = "this.form['f-_order_by'].value='$k_wsk_id';this.form['f-_order_desc'].value='desc';";
							$out .= " " . '<input type="image" onclick="' . $js . '" src="icon/sort-desc.gif" alt="sort-desc" title="' . "Sortuj malejąco" . '">';
						} else {
							$js = "this.form['f-_order_by'].value='$k_wsk_id';this.form['f-_order_desc'].value='asc';";
							$out .= " " . '<input type="image" onclick="' . $js . '" src="icon/sort-asc.gif" alt="sort-desc" title="' . "Sortuj rosnąco" . '">';
						}
					} else {
						$js = "this.form['f-_order_by'].value='$k_wsk_id';this.form['f-_order_desc'].value='desc';";
						$out .= " " . '<input type="image" onclick="' . $js . '" src="icon/sort-desc.gif" alt="sort-desc" title="' . "Sortuj malejąco" . '">';
					}
				$out .= '</th>';
			}
		$out .= '</tr>';
		$out .= '<tr class="tbl-filters">';
			$out .= '<th>';
				$out .= '<input type="image" value="' . "Szukaj" . '" src="icon/search.png" title="' . "Szukaj" . '">';
				if ($filters_set) {
					$js = "return resetForm(this);";
					$out .= " " . '<input type="image" value="' . "Szukaj" . '" onclick="' . $js . '" src="icon/shutdown.gif" title="' . "Czyść" . '">';
				}
			$out .= '</th>';
			foreach ($cols as $k_wsk_id => $v_field) {
				$filter_field_name = 'f-' . $k_wsk_id;
				$readonly = '';
				if (array_key_exists($k_wsk_id, $this->_filters_readonly)) {
					$readonly = 'readonly="readonly"';
				}
				$out .= '<th>';
					$out .= '<input type="text" name="' . $filter_field_name . '" value="' . $this->get_filter($filter_field_name) . '" ' . $readonly . ' class="i" />';
				$out .= '</th>';
			}
		$out .= '</tr>';
		$out .= '</thead>';
		$out .= '<tfoot>';
		$out .= '<tr>';
			$out .= '<td colspan="' . (count($cols) + 1) . '">';
				$out .= $nav->render();
			$out .= '</td>';
		$out .= '</tr>';
		$out .= '</tfoot>';
		$out .= '<tbody>';
		if (empty($rows)) {
			$out .= '<tr>';
				$out .= '<td colspan="' . (count($cols) + 1) . '">';
					$out .= '<p>' . '<b style="color:red">' . "Brak danych!" . '</b>';
						if ($rows_total > 0) {
							$out .= " Przejdź do " . '<a href="' . $nav->get_page_link(1) . '">' . "pierwszej strony" . '</a>';
						}
					$out .= '</p>';
				$out .= '</td>';
			$out .= '</tr>';
		} else {
			$t = 0;
			foreach ($rows as $k_row => $v_row) {
				$out .= '<tr class="row-' . ($t = 1 - $t) . '">';
				$out .= '<td>';
					$js = "this.form['_action'].value='';this.form['_id_key'].value='"  .$v_row->f_ID . "';";
					$out .= '<input type="submit" value="' . "E" . '" onclick="' . $js . '" class="btn" />';
					$js = "this.form['_action'].value='select_id_key';this.form['_id_key'].value='"  .$v_row->f_ID . "';";
					$out .= '<input type="submit" value="' . "W" . '" title="' . "Wybierz rekord " . $v_row->f_ID . '" onclick="' . $js . '" class="btn" />';
				$out .= '</td>';
				foreach ($cols as $k_field => $v_field) {
					$sql_field_name = 'f_' . $k_field;
					$out .= '<td>';
						$out .= $v_row->$sql_field_name;
					$out .= '</td>';
				}
				$out .= '</tr>';
			}
		}
		$out .= '</tbody>';
		$out .= '</table>';

		return $out;
	}

	function action_save_edit() {
		// TODO: validate input
		// save $this->_v into zasob source
		$db = DB::getDB();
		Lib::loadClass('BaseDao');
		Lib::loadClass('BaseModel');
		$dao = new BaseDao($db, $this->get_table_name());

		if (!$this->_id_key) {
			echo '<p>' . "Error: brak ID rekordu!" . '</p>';
			return null;
		}
		$model = $dao->get_by_id($this->_id_key);
		if (!$model) {
			echo '<p>' . "Error: rekord [".$this->_id_key."] nie istnieje!" . '</p>';
			return null;
		}

		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$model->set($v_wsk->DESC, $this->get($k_wsk_id));
		}
		$ret = $dao->save($model);
		if (!$ret) {
			return null;
		}
		return $dao;
	}

	function action_select_id_key() {
		// TODO: validate input
		// save $this->_v into zasob source
		$db = DB::getDB();
		Lib::loadClass('BaseDao');
		Lib::loadClass('BaseModel');
		$dao = new BaseDao($db, $this->get_table_name());

		if (!$this->_id_key) {
			echo '<p>' . "Error: brak ID rekordu!" . '</p>';
			return null;
		}
		$model = $dao->get_by_id($this->_id_key);
		if (!$model) {
			echo '<p>' . "Error: rekord [".$this->_id_key."] nie istnieje!" . '</p>';
			return null;
		}
		$this->set_type('read');
		return $model;
	}

	function get_table_name() {
		static $tbl_name;
		if (!$tbl_name) {
			$zasob_tbl = DB::get_by_id('CRM_LISTA_ZASOBOW', $this->_tbl_zasob_id);
			if (!$zasob_tbl) {
				$tbl_name = null;
			}
			$tbl_name = $zasob_tbl->DESC;
		}
		return $tbl_name;
	}

	function _sql_where() {
		$sql_where = '';
		$sql_where_and_arr = array();
		$db = DB::getDB();

		foreach ($this->_filters as $k_wsk_id => $v_val) {
			if ($k_wsk_id == 'ID') {
				$sql_field = 'ID';
			} else {
				if (!array_key_exists($k_wsk_id, $this->_komorki)) {
					continue;
				}
				$sql_field = $this->_komorki[$k_wsk_id]->DESC;
			}
			if ($v_val != '%') {
				$sql_where_and_arr[] = "t.`" . $sql_field . "` like '" . $db->_($v_val) . "'";
			}
		}

		$sql_where = (!empty($sql_where_and_arr))? "where " . implode(" and ", $sql_where_and_arr) . "" : "";

		return $sql_where;
	}

	function fetch_rows_total() {
		$ret = 0;
		$db = DB::getDB();
		$sql_table = $this->get_table_name();
		$sql_where = $this->_sql_where();
		$sql = "select count(1) as cnt
			from `" . $sql_table . "` as t
			" . $sql_where . "
		";
		$res = $db->query($sql);
		if ($r = $db->fetch($res)) {
			$ret = $r->cnt;
		}
		return $ret;
	}

	function fetch_rows() {
		$rows = array();
		$db = DB::getDB();

		$sql_select = array();// as f_ID, f_{$ID_ZASOB}
		$sql_table = $this->get_table_name();
		$sql_where = $this->_sql_where();
		$sql_order_by = 'order by t.`ID` DESC';// default
		$sql_limit = 'limit 10';// default
		$offset = V::get('f-_offset', 0, $this->_nav_filters);
		if ($offset) {
			$sql_limit .= ' offset ' . $offset;
		}
		$sql_order_by_wsk_id = $this->get_nav_filter('f-_order_by');
		if (array_key_exists($sql_order_by_wsk_id, $this->_komorki)) {
			$sql_order_by = 'order by t.`' . $this->_komorki[$this->get_nav_filter('f-_order_by')]->DESC . '` ' . $this->get_nav_filter('f-_order_desc');
		} else if ($sql_order_by_wsk_id == 'ID') {
			$sql_order_by = 'order by t.`ID` ' . $this->get_nav_filter('f-_order_desc');
		}

		$sql_select[] = "t.`ID` as f_ID";
		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$sql_select[] = "t.`" . $v_wsk->DESC . "` as f_" . $k_wsk_id;
		}

		$sql = "select " . implode("," , $sql_select) . "
			from `" . $sql_table . "` as t
			" . $sql_where . "
			" . $sql_order_by . "
			" . $sql_limit . "
		";
		$res = $db->query($sql);
		while ($r = $db->fetch($res)) {
			$rows[] = $r;
		}
		return $rows;
	}

	function fetch_row() {
		$row = null;
		if (!$this->_id_key) {
			return $row;
		}
		$db = DB::getDB();

		$sql_select = array();// as f_ID, f_{$ID_ZASOB}
		$sql_table = $this->get_table_name();
		$sql_where = "";//$this->_sql_where();

		$sql_where .= (($sql_where)? " and " : " where ") . " t.`ID`='" . $this->_id_key . "' ";

		$sql_select[] = "t.`ID` as f_ID";
		foreach ($this->get_komorki() as $k_wsk_id => $v_wsk) {
			$sql_select[] = "t.`" . $v_wsk->DESC . "` as f_" . $k_wsk_id;
		}

		$sql = "select " . implode("," , $sql_select) . "
			from `" . $sql_table . "` as t
			" . $sql_where . "
		";
		$res = $db->query($sql);
		if ($r = $db->fetch($res)) {
			$row = $r;
		}
		return $row;
	}

	function get_field_type($wsk_id) {
		if (!array_key_exists($wsk_id, $this->_komorki)) {
			return null;
		}
		$wsk = $this->_komorki[$wsk_id];
		if (isset($wsk->_sql_type)) {
			return $wsk->_sql_type;
		}
		return null;
	}

	/**
	 * @param $sql_types array( Field => array(
	 *   [Type] => int(10)
	 *   [Null] => NO
	 *   [Key] => PRI
	 *   [Default] => 
	 *   [Extra] => auto_increment )
	 */
	function set_komorki_sql_types($sql_types) {
		foreach ($this->_komorki as $k_wsk_id => $v_wsk) {
			if (array_key_exists($v_wsk->DESC, $sql_types)) {
				$this->_komorki[$k_wsk_id]->_sql_type = $sql_types[$v_wsk->DESC];
			}
		}
	}

	/**
	 * @returns array of data - $this object as simple array to store in session.
	 */
	function to_array() {
		$arr = array();
		$arr['_tbl_zasob_id'] = $this->_tbl_zasob_id;
		$arr['_komorki'] = $this->_komorki;
		$arr['_last_id_key'] = $this->_last_id_key;
		return $arr;
	}

	/**
	 * @param $state array of data - object state
	 */
	function from_array($arr) {
		$this->_tbl_zasob_id = $arr['_tbl_zasob_id'];
		$this->_komorki = $arr['_komorki'];
		$this->_last_id_key = $arr['_last_id_key'];
	}

	/**
	 * @returns array of data - current state as simple array to store in session.
	 */
	function state_to_array() {
		$state = array();
		$state['_v'] = $this->_v;
		$state['_filters'] = $this->_filters;
		$state['_nav_filters'] = $this->_nav_filters;
		$state['_id_key'] = $this->_id_key;
		return $state;
	}

	/**
	 * @param $state array of data - object state
	 */
	function state_from_array($state) {
		if (!empty($state['_v'])) $this->_v = $state['_v'];
		if (!empty($state['_filters'])) $this->_filters = $state['_filters'];
		if (!empty($state['_nav_filters'])) $this->_nav_filters = $state['_nav_filters'];
		//if (!empty($state['_id_key'])) $this->_id_key = $state['_id_key'];
	}

	/**
	 * Save state in session.
	 *   _filters - array of table filter values
	 *   _nav_filters - array of table nav filter values
	 *   _v - array of form field values
	 *   _id_key - Record ID
	 */
	function save_state() {
		if (!array_key_exists('PROCES_LOG_STATE', $_SESSION)) {
			$_SESSION['PROCES_LOG_STATE'] = array();
			$_SESSION['PROCES_LOG_STATE']['_change_ts'] = array();// used in garbage collection
		}

		$state = $this->state_to_array();

		$ses_key = $this->_log_id . '-' . $this->_log_wsk_id;

		$_SESSION['PROCES_LOG_STATE'][$ses_key] = $state;

		if (array_key_exists($ses_key, $_SESSION['PROCES_LOG_STATE']['_change_ts'])) {
			$_SESSION['PROCES_LOG_STATE']['_change_ts'][$ses_key] = time();
		} else {
			$_SESSION['PROCES_LOG_STATE']['_change_ts'][$ses_key] = time();
		}

		$this->_garbage_collection();
	}

	/**
	 * Read state from session.
	 * @see save_state
	 */
	function read_state() {
		$ses_key = $this->_log_id . '-' . $this->_log_wsk_id;
		if (isset($_SESSION['PROCES_LOG_STATE'][$ses_key])) {
			$this->state_from_array($_SESSION['PROCES_LOG_STATE'][$ses_key]);
		}
	}

	/**
	 * Garbage collection (GC) is a form of automatic memory management.
	 */
	function _garbage_collection() {
		$max_cache_count = 10;
		if (count($_SESSION['PROCES_LOG_STATE']) <= $max_cache_count) {
			return;
		}
		// TODO: sort table _change_ts by value with keys - [$ses_key] = time();
		// TODO: remove row from ses by _change_ts
	}

	function get_step_wsk_desc() {
		$ret = '';
		if (!empty($this->_komorki)) {
			$first_wsk = reset($this->_komorki);
			$ret = '(' . $first_wsk->DESC . ') ';
			if ($first_wsk->OPIS_ZASOB) {
				$ret .= $first_wsk->OPIS_ZASOB;
			} else {
				$ret .= $first_wsk->OPIS;
			}
		}
		return $ret;
	}

	function allow_create() {
		foreach ($this->_komorki as $k_wsk_id => $v_komorka) {
			// CP_FORM_TREAT => R,W,X,C
			if (in_array('C', explode(',', $v_komorka->CP_FORM_TREAT))) {
				return true;
			}
		}
		return false;
	}

	function allow_write() {
		foreach ($this->_komorki as $k_wsk_id => $v_komorka) {
			// CP_FORM_TREAT => R,W,X,C
			if (in_array('W', explode(',', $v_komorka->CP_FORM_TREAT))) {
				return true;
			}
		}
		return false;
	}

	function allow_read() {
		foreach ($this->_komorki as $k_wsk_id => $v_komorka) {
			// CP_FORM_TREAT => R,W,X,C
			if (in_array('R', explode(',', $v_komorka->CP_FORM_TREAT))) {
				return true;
			}
		}
		return false;
	}

}
