ACC SHELL

Path : /srv/www/vhosts/alfa-com/engine/
File Upload :
Current File : /srv/www/vhosts/alfa-com/engine/Object.php

<?php
	// Author: Jakub Macek, CZ; Copyright: Poski.com s.r.o.; Code is 100% my work. Do not copy.

	class Object
	{
		public static			$_									= null;
		public static			$_query_cache						= array();
		public static			$_fields							= array();

		public					$i									= null;
		public					$d									= array();
		public					$o									= array();
		public					$x									= array();

		public static function instantiate($id, $class, $options = array())
		{
			if ($temp0 = o($id))
				return $temp0;
			$object = new $class();
			$object->i = $id;
			$GLOBALS['objects'][$id] = $object;
			if (isset($GLOBALS['settings']['objects'][$object->i]))
				$GLOBALS['settings']['objects'][$object->i] = array_merge($GLOBALS['settings']['objects'][$object->i], $options);
			else
				$GLOBALS['settings']['objects'][$object->i] = $options;
			self::$_fields[$object->i] = new Fields();
			/*if ($module = $object->get('module'))
				if ($options = $module->get('object-options'))
					$GLOBALS['settings']['objects'][$object->i] = array_merge($GLOBALS['settings']['objects'][$object->i], $options);*/

			if (!$object->get('table'))
				$object->set('table', site()->tablePrefix . $object->i);

			for ($phase = 0; $phase < 8; $phase++)
				$object->initialize($phase);

			return $object;
		}

		public function prefix($value = '')
		{
			return $this->i . '-' . $value;
		}

		public function prefixField($text = '')
		{
			return $text;
		}

		public function module()
		{
			return m($this->get('module'));
		}

		public function options()
		{
			return $GLOBALS['settings']['objects'][$this->i];
		}

		public function set($key, $value = null)
		{
			if ($value === null)
				unset($GLOBALS['settings']['objects'][$this->i][$key]);
			else
				$GLOBALS['settings']['objects'][$this->i][$key] = $value;
		}

		public function get($key, $default = null)
		{
			return isset($GLOBALS['settings']['objects'][$this->i][$key]) ? $GLOBALS['settings']['objects'][$this->i][$key] : $default;
		}

		public function initialize($phase)
		{
			if ($phase == 0)
			{
				$this->define('oid', Field::TYPE_STRING, 64, '', array('forms' => false));
				$this->define('ostatus', Field::TYPE_STRING, false, 'new', array(
					'select' => true,
					'enum' => true,
					'values' => array('normal' => 'normal', 'new' => 'new', 'deleted' => 'deleted'),
					'forms' => false,
				));
				$this->define('otitle', Field::TYPE_STRING, false, '', array('forms' => false), false, false);
				$this->define('g', Field::TYPE_BOOLEAN, false, false, array(), 'return ((!$object->oid) || (isset($object->group) && $object->group));', false);
	
				$this->define('primary_key', Field::TYPE_PRIMARY_KEY, 0, array('oid'));
	
				$this->defineACL($this->get('fake-acl', true));
				$this->defineAttributes($this->get('fake-attributes', true));
			}
			
			if ($phase == 4)
			{
				foreach ($this->get('fields', array()) as $field)
					$this->define(clone($field));
	
				foreach ($this->fields()->keys() as $fid)
					if ($reference = $this->f($fid)->get('reference'))
						$this->f($fid)->absorb(o($reference[0])->f($reference[1]));
	
				if ($modifications = $this->get('modifications'))
					foreach ($modifications as $modification)
						callback($modification, array('object' => $this));
			}
		}

		public function define($id, $type = Field::TYPE_NULL, $size = null, $default = null, $options = array(), $get = null, $set = null)
		{
			if (!is_object($id))
				$id = new Field($id, $type, $size, $default, $options, $get, $set);
			$field = $id;
			$this->fields()->{$field->id} = $field;
			$field->object = $this->i;
			Field::$_[$field->key()] = $field;
			if ($field->get('localized'))
				$field->localize();

			if ($field->get('select'))
			{
				/*$this->define(
					$field->id . '+',
					Field::TYPE_STRING,
					false,
					'',
					array(
						'forms' => array(),
					),
					'return $object->f(\'' . $field->id . '\')->select($object->f(\'' . $field->id . '\')->get($object));',
					false
				);*/
				$this->define(
					$field->id . '__0',
					Field::TYPE_STRING,
					false,
					'',
					array(
						'forms' => array(),
					),
					'return $object->f(\'' . $field->id . '\')->select($object->f(\'' . $field->id . '\')->get($object));',
					false
				);
			}

			if (($field->type == Field::TYPE_DATETIME) || ($field->type == Field::TYPE_DATE))
			{
				$default_format = 'Y-m-d H:i:s';
				if ($field->type == Field::TYPE_DATE)
					$default_format = 'Y-m-d';
				/*$this->define(
					$field->id . '+',
					Field::TYPE_STRING,
					false,
					date($field->get('format', $default_format), 0),
					array(
						'forms' => array(),
						'format' => $field->get('format'),
					),
					'return date($field->get("format", "'.$default_format.'"), $object->{\'' . $field->id . '\'});',
					'$object->{\'' . $field->id . '\'} = strtotime($value);'
				);*/
				$this->define(
					$field->id . '__0',
					Field::TYPE_STRING,
					false,
					date($field->get('format', $default_format), 0),
					array(
						'forms' => array(),
						'format' => $field->get('format'),
					),
					'return date($field->get("format", "'.$default_format.'"), $object->{\'' . $field->id . '\'});',
					'$object->{\'' . $field->id . '\'} = strtotime($value);'
				);
			}

			return $field;
		}

		public function defineACL($fake = false)
		{
			$this->define('ouid', Field::TYPE_STRING, 64, '', array(
				'select' => array('@users', 'select_id'),
				'forms' => array('~acl~'),
			));
			$this->define('ouser', Field::TYPE_CONNECTION, null, array('object' => '@users', 'fields' => array('ouid' => 'id')), array(
				'one' => true,
			));

			$this->define('orid', Field::TYPE_STRING, 64, '', array(
				'forms' => array('~acl~'),
			));

			/*$this->define('oacl', Field::TYPE_CONNECTION, null, array('object' => 'acl', 'fields' => array('oid' => 'pid')), array(
				'recurse' => true,
				'forms' => false,
			));*/
			//HACK $this->define('oacl', Field::TYPE_OBJECT, 64, array(), array('forms' => array()));
			$this->define('oacl', Field::TYPE_OBJECT, false, array(), array('forms' => array()));

			if ($fake)
			{
				$this->f('ouid')->size = false;
				$this->f('orid')->size = false;
				$this->f('oacl')->size = false;
			}
		}

		public function defineAttributes($fake = false)
		{
			if (!m('@attributes'))
				$fake = true;

			$this->define('oattrs', Field::TYPE_STRING, 255, '', array(
				'forms' => array('~acl~'),
				'multi' => ';',
			));
			if (!$fake)
				$this->f('oattrs')->set('select', array('@attribute_types', 'select_key'));
			$this->define('oattributes', Field::TYPE_OBJECT, 65535, array(), array('forms' => array()));

			if ($fake)
			{
				$this->f('oattrs')->size = false;
				$this->f('oattributes')->size = false;
			}
		}

		public function defineV($id, $type = Field::TYPE_NULL, $size = false, $default = null, $options = array(), $get = null, $set = null)
		{
			$field = $this->define($id, $type, $size, $default, $options, $get, $set);
			$field->set('forms', array());
			return $field;
		}

		public function defineOCreated()
		{
			$f = $this->define('ocreated', Field::TYPE_DATETIME, null, 0, array('forms' => array()));
			return $f;
		}

		public function defineOModified()
		{
			$f = $this->define('omodified', Field::TYPE_DATETIME, null, 0, array('forms' => array()));
			return $f;
		}

		public function defineTimestamp($id = 'timestamp', $default = 0)
		{
			$f = $this->define($id, Field::TYPE_DATETIME, null, $default/*, array('forms' => array())*/);
			return $f;
		}

		public function defineBlob($full = false, $id = 'blob')
		{
			$f = $this->define($id, Field::TYPE_FILE, null, '');
			$f->set('image-variants', array(array('scaleexpand', '.thumb', 200, 200, 'ffffff')));
			if ($full)
			{
				$f->set('size', $id.'_size');
				$f->set('type', $id.'_type');
				$f->set('name', $id.'_name');
				$this->define($id.'_size', Field::TYPE_INTEGER, null, 0, array('forms' => array()));
				$this->define($id.'_type', Field::TYPE_STRING, 255, 0, array('forms' => array()));
				$this->define($id.'_name', Field::TYPE_STRING, 255, 0, array('forms' => array()));
			}
			return $f;
		}

		public function defineImage($id = 'image')
		{
			$f = $this->define($id, Field::TYPE_FILE, null, '');
			$f->set('image-variants', array(array('scaleexpand', '.thumb', 200, 200, 'ffffff')));
			return $f;
		}

		public function defineAttachments($id = 'attachments')
		{
			$f = $this->define($id, Field::TYPE_OBJECT, 65535, array());
			$f->set('files', true);
			return $f;
		}

		public function defineImages($id = 'images')
		{
			$f = $this->define($id, Field::TYPE_OBJECT, 65535, array());
			$f->set('files', true);
			$f->set('image-variants', array(array('scaleexpand', '.thumb', 200, 200, 'ffffff')));
			return $f;
		}

		public function defineGroup($default = false)
		{
			$f = $this->define('group', Field::TYPE_BOOLEAN, null, $default);
			return $f;
		}

		public function definePriority()
		{
			$f = $this->define('priority', Field::TYPE_INTEGER, null, 0);
			return $f;
		}

		public function defineString($id, $size = 255, $default = '', $options = array())
		{
			$f = $this->define($id, Field::TYPE_STRING, $size, $default, $options);
			return $f;
		}
		
		public function defineBoolean($id, $default = false, $options = array())
		{
			$f = $this->define($id, Field::TYPE_BOOLEAN, null, $default, $options);
			return $f;
		}

		public function defineStringLocalized($id, $size = 255, $default = '', $options = array())
		{
			$f = $this->defineString($id, $size, $default, $options);
			$f->localize();
			return $f;
		}

		public function defineText($id, $size = 65535, $default = '', $options = array())
		{
			$f = $this->define($id, Field::TYPE_TEXT, $size, $default, $options);
			return $f;
		}

		public function defineTextLocalized($id, $size = 65535, $default = '', $options = array())
		{
			$f = $this->defineText($id, $size, $default, $options);
			$f->localize();
			return $f;
		}

		public function defineTextHTML($id, $size = 65535, $default = '', $options = array())
		{
			$f = $this->defineText($id, $size, $default, $options);
			$f->set('html', true);
			return $f;
		}

		public function defineTextLocalizedHTML($id, $size = 65535, $default = '', $options = array())
		{
			$f = $this->defineTextHTML($id, $size, $default, $options);
			$f->localize();
			return $f;
		}		

		public function defineIdAutoIncrement()
		{
			$f = $this->define('id', Field::TYPE_INTEGER, null, 0, array('forms' => array(), 'autoincrement' => true));
			$this->define('index_id', Field::TYPE_INDEX, null, array('id'));
			return $f;
		}

		public function definePid($parent = null)
		{
			if (!$parent)
				$parent = $this->i;
			$f = $this->define('pid', Field::TYPE_STRING, 64, '', array(
				'forms' => array(),
				'reference' => array($parent, 'oid'),
			));
			$this->define('connection_parent', Field::TYPE_CONNECTION, null, array('object' => $parent, 'fields' => array('pid' => 'oid')), array(
				'one' => true,
			));
			if (method_exists(m($parent), 'select_oid'))
				$f->set('select', array($parent, 'select_oid'));
			$this->define('index_pid', Field::TYPE_INDEX, null, array('pid'));
			return $f;
		}

		public function defineUnique($fields, $name = null)
		{
			if (!$name)
			{
				$name = 'index';
				foreach ($fields as $field)
					$name .= '_' . $field;
			}
			$f = $this->define($name, Field::TYPE_UNIQUE, null, $fields);
			return $f;
		}

		public function defineIndex($fields, $name = null)
		{
			if (!$name)
			{
				$name = 'index';
				foreach ($fields as $field)
					$name .= '_' . $field;
			}
			$f = $this->define($name, Field::TYPE_INDEX, null, $fields);
			return $f;
		}

		public function localize($field)
		{
			foreach (site()->locales as $locale)
			{
				$temp = clone($this->f($field));
				$temp->id = $field . '_' . $locale;
				$temp->set('localized', false);
				$temp->set('localized-field', $field);
				$temp->set('localized-locale', $locale);
				$this->fields()->_[$temp->id] = $temp;
			}
			$this->f($field)->set('localized', true);
			$this->f($field)->size = false;
		}

		public function fields($fields = null, $clone = false)
		{
			if ($fields === null)
			{
				$result = self::$_fields[$this->i];
				if ($clone)
					$result = clone($result);
				return $result;
			}

			$temp1 = array();
			foreach ($fields as $k => $v)
				if (is_object($v))
					$temp1[] = $v;
				else if (is_int($k) && is_string($v))
					$temp1[] = $this->f($v);
				else
					$temp1[] = $this->f($k);

			$temp2 = array();
			$temp2['^'] = null;
			foreach ($temp1 as $field)
				$temp2[$field->id] = $field;
			$temp2['$'] = null;

			$temp3 = array();
			foreach ($temp2 as $id => $field)
				if (!$field || (!$field->get('form-before') && !$field->get('form-after')))
				{
					$temp3[$id] = $field;
					unset($temp2[$id]);
				}

			$work = true;
			while ($work)
			{
				$work = false;
				foreach ($temp2 as $id => $field)
				{
					if (($before = $field->get('form-before')) && isset($temp3[$before]))
					{
						$temp3 = array_insert($temp3, $before, array($id => $field), true);
						unset($temp2[$id]);
						$work = true;
					}
					if (($after = $field->get('form-after')) && isset($temp3[$after]))
					{
						$temp3 = array_insert($temp3, $after, array($id => $field), false);
						unset($temp2[$id]);
						$work = true;
					}
				}
			}
			unset($temp3['^']);
			unset($temp3['$']);

			$result = new Fields();
			foreach ($temp3 as $field)
				$result->add($field);
			return $result;
		}

		public function f($id)
		{
			if (!isset($this->fields()->$id))
				return null;
			return $this->fields()->$id;
		}

		public function attributes($attributes = null)
		{
			return Fields::attributes($attributes, $this->oattrs);
		}

		public function __get($key)
		{
			$field = $this->f($key);
			if (!$field)
				error('unknown field: ' . $key);
			return $field->get($this);
		}

		public function __set($key, $value)
		{
			if (substr($key, -12) == '_all_locales')
			{
				$key = substr($key, 0, strlen($key) - 12);
				foreach (site()->locales as $locale)
					$this->{$key . '_' . $locale} = $value;
			}
			else
			{
				$field = $this->f($key);
				if (!$field)
					error('unknown field: ' . $key);
				$field->set($this, $value);
			}
		}

		public function __isset($key)
		{
			return isset($this->fields()->$key);
		}

		public function clear()
		{
			foreach ($this->fields() as $field)
				$field->clear($this);
		}

		public function acl($invocation, $action)
		{
			return null;
		}

		public function listStyle($column = null)
		{
		}

		public function dataGet($key, $default = null)
		{
			return (isset($this->data[$key]) ? $this->data[$key] : $default);
		}

		public function dataSet($key, $value = null)
		{
			$data = $this->data;
			if ($value === null)
				unset($data[$key]);
			else
				$data[$key] = $value;
			$this->data = $data;
		}

		public function attributeGet($key, $default = null)
		{
			$attributes = $this->attributes();
			if (!isset($attributes->$key))
				return $default;
			return $attributes->$key->get($this, $default);
		}

		public function attributeSet($key, $value = null)
		{
			$attributes = $this->attributes();
			if (!isset($attributes->$key))
				return null;
			return $attributes->$key->set($this, $value);
		}

		public function event($ev)
		{
			switch ($ev)
			{
				case 'load.before':
					break;
				case 'load.after':
					$this->o = $this->d;
					foreach ($this->fields() as $field)
						if ($field->type == Field::TYPE_OBJECT)
							$this->o[$field->id] = $field->default;
					break;
				case 'save.before':
					if (isset($this->ocreated) && ($this->ocreated == 0))
						$this->ocreated = time();

					if (isset($this->omodified))
						$this->omodified = time();

					if (isset($this->id) && $this->f('id')->get('autoincrement') && ($this->id == 0))
						$this->generate(0, 'id');

					if (!$this->ouid && m('@users') && defined('USER') && USER)
						$this->ouid = USER;

					if (o('@attributes'))
					{
						$this->oattributes = array();
						$attributes = o('@attributes')->load(Feq('object', $this->oid));
						$as = array();
						foreach ($attributes as $attribute)
							$as[$attribute->key] = $attribute->getValue();
						$this->oattributes = $as;
					}

					foreach ($this->fields() as $field)
						if ($field->type == Field::TYPE_CONNECTION)
						{
							if (!$field->get('recurse'))
								break;
							if (!isset($event['object']->x[$field->id]))
								break;
							$info = $field->connection($event['object']);
							foreach ($event['object']->x[$field->id] as $o)
								foreach ($info['connected'] as $key => $value)
									$o->$key = $value;
							foreach ($event['object']->x[$field->id] as $o)
								$o->save();
							break;
						}

					break;
				case 'save.after':
					break;
				case 'delete.before':
					break;
				case 'delete.after':
					foreach ($this->fields() as $field)
						if ($field->type == Field::TYPE_CONNECTION)
						{
							if (!$field->get('recurse'))
								break;
							$info = $field->connection($event['object']);
							$info['object']->delete($info['filter']);
						}

					if (m('@attributes'))
						o('@attributes')->delete(Feq('object', $this->oid));
					break;
			}
		}

		public function generate($value = null, $fid = 'oid', $method = null)
		{
			if ($fid == 'oid')
				$method = 'oid';
			$field = $this->f($fid);

			if ($value === 0)
				$method = 'sequence';

			switch ($method)
			{
				case 'date':
					if ($this->$fid)
						return;
					$format = (($field->type == Field::FIELD_TYPE_INTEGER) ? 'dhis' : 'Ymd-his-');
					$counter = 0;
					do
						$temp = date($format) . sprintf('%04d', $counter++);
					while ($this->count(Feq($field, $temp)));
					$this->$fid = $temp;
					break;
				case 'sequence':
					if ($this->$fid)
						return;
					$query = 'SELECT MAX(CAST(' . qi($fid) . ' AS UNSIGNED)) FROM ' . qi($this->get('table'));
					$max = (int) qo($query);
					$this->$fid = $max + 1;
					break;
				case 'rewrite':
					if (!$value)
						return;
					$value = substr(U::urlize($value), 0, $field->size - 5) . '-';
					$counter = 0;
					$temp = substr(U::urlize($value), 0, $field->size);
					while (qo("SELECT COUNT(".qi('oid').") FROM ".qi($this->get('table'))." WHERE ".qi('oid')." != ".qq($this->oid)." AND ".qi($field->id)." = ".qq($temp)))
						$temp = $value . sprintf('%04d', $counter++);
					$this->$fid = $temp;
					break;
				case 'oid':
				default:
					if ($this->$fid)
						return;
					$this->$fid = Session::oid($this->i);
					break;
			}
		}

		public function form($operation, $group, $fields = null, $invocation = null, $options = array())
		{
			$fields = clone($this->fields($fields));
			foreach ($fields as $field)
				$field->form($operation, $group, $this, $invocation, $options);
		}

		public function parents($first = false)
		{
			if (isset($this->x['parent']) && $first)
				return $this->x['parent'];
			if (isset($this->x['parents']) && !$first)
				return $this->x['parents'];
			$result = new Objects();

			$parent = null;
			if ($fid = $this->get('parent-connection'))
			{
				$field = $this->f($fid);
				$info = $field->connection($this);
				$parent = $info['object']->load($info['filter'], true);
				if (!$parent)
				{
					$parent = clone($info['object']);
					$parent->x['placeholder'] = true;
				}
			}
			else if ($this->f('pid'))
			{
				$reference = $this->f('pid')->get('reference');
				if (o($reference[0]) && $this->pid)
					$parent = o($reference[0])->load(Feq($reference[1], $this->pid), true);
			}

			$this->x['parent'] = $parent;
			if ($first)
				return $parent;
			if ($parent)
			{
				$result->{null} = $parent;
				$result->add($parent->parents());
			}

			$this->x['parents'] = $result;
			return $result;
		}

		public function children($key = null)
		{
			//TODO dodelat reference - vice typu potomku
			if (isset($this->x['children']))
				$result = $this->x['children'];
			else
			{
				$result = array();
				if ($this->f('pid'))
					$result[0] = $this->load(Feq('pid', $this->oid));
				else
					$result[0] = new Objects();
				$this->x['children'] = $result;
			}
			if ($key !== null)
				$result = $result[$key];
			return $result;
		}

		public function queryCache($query = null, $result = null)
		{
			if ($query === true) // start cache
				self::$_query_cache = array();
			else if ($query === false) // stop cache
				self::$_query_cache = null;
			else if (self::$_query_cache === null) // if stopped, exit
				return null;
			else if (($query === null) && ($result === null)) // clear
				self::$_query_cache = array();
			elseif ($result === null) // get
				return (isset(self::$_query_cache[$query]) ? self::$_query_cache[$query] : null);
			else // set
				self::$_query_cache[$query] = $result;
			return null;
		}

		public function sqlTable()
		{
			return qi($this->get('table'));
		}

		public function cast($o)
		{
			$result = clone($this);
			if (is_object($o))
				$result->d = $o->d;
			else
				$result->d = $o;
			return $result;
		}

		public function fromArray($rows)
		{
			$fid_oid = $this->prefixField('oid');
			$objects = new Objects();
			$fields = $this->fields();
			foreach ($rows as $row)
			{
				$oid = null;
				if (isset($row[$fid_oid]))
					$oid = $row[$fid_oid];
				if (isset(self::$_->$oid))
					$objects->$oid = self::$_->$oid;
				else if ($oid && isset($objects->$oid))
					;
				else
				{
					$object = clone($this);
					//Event::invoke(array("object.load.before", "object.{$this->i}.load.before"), array('object' => $object));
					$object->event('load.before');
					foreach ($row as $k => $v)
						if (($v !== null) && isset($fields->$k))
							$object->$k = $v;
					$object->ostatus = 'normal';
					$object->event('load.after');
					//Event::invoke(array("object.load.after", "object.{$this->i}.load.after"), array('object' => $this));
					$objects->{null} = $object;
				}
			}
			return $objects;
		}

		public function create()
		{
			$this->drop();
			$query = array();
			foreach ($this->fields() as $field)
				if (!($field->type & Field::TYPE_MASK_INDEX))
					if ($temp = $field->sqlDefinition($this))
						$query[] = "\t" . $temp;
			foreach ($this->fields() as $field)
				if ($field->type & Field::TYPE_MASK_INDEX)
					if ($temp = $field->sqlDefinition($this))
						$query[] = "\t" . $temp;

			$query = "CREATE TABLE ".qi($this->get('table'))." (\n" . implode(",\n", $query) . "\n";
			if ($temp = $this->get('create-table-0'))
				$query .= $temp;
			$query .= ")";
			//$query .= "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci";
			$result = q($query);
		}

		public function exists()
		{
			$GLOBALS['logs'][0]->skipNext = 2;
			$query = "SHOW TABLES LIKE '{$this->get('table')}'";
			$result = qa($query);
			return count($result) ? true : false;
		}

		public function drop()
		{
			$result = q("DROP TABLE IF EXISTS ".qi($this->get('table'))."");
		}

		public function sqlWhere($filter)
		{
			if ($filter)
				return $filter->sql(array('object' => $this));
			else
				return null;
		}

		public function sqlPrepareCallback($matches)
		{
			$match = str_replace('~', $this->i, $matches[1]);
			if (strpos($match, '.') !== false)
			{
				$parts = explode('.', $match);
				$fid = array_pop($parts);
				$oid = array_shift($parts);
				foreach ($parts as $temp)
				{
					if (!($object = @o($oid)))
						return '[error-1]';
					if (!($field = $this->f($temp)))
						return '[error-2]';
					if (($field->type != Field::TYPE_CONNECTION) || !isset($field->default['object']))
						return '[error-3]';
					$oid = $field->default['object'];
				}
				if (!($object = @o($oid)))
					return '[error-4]';
				if (!($field = $object->f($fid)))
					return '[error-5]';
				return $field->sqlName();
			}
			else
			{
				if (!($object = @o($match)))
					return '[error]';
				return qi($this->get('table'));
			}
		}

		public function sqlSelect($filter = null, $order = null, $limit = null, $count = false)
		{
			if ($filter && !is_object($filter))
				error('where: ' . gettype($filter));
			$query = "SELECT " . ($count ? "COUNT(DISTINCT {$this->f('oid')->sqlName()})" : "*") . " FROM ".qi($this->get('table'))." ".qi($this->i);
			if ($filter)
			{
				$filter = $filter->sql(array('object' => $this));
				$filter = trim($filter);
				if ($filter)
					$query .= " WHERE " . $filter;
			}

			if (($order === null) || ($order === false))
				$order = $this->get('order');
			if ($order && is_array($order) && count($order))
			{
				$o = array();
				foreach ($order as $temp)
				{
					$field = str_replace(array('+', '-'), array('', ''), $temp);
					if (!$this->f($field))
						error("missing field '$field' in object '{$this->i}'");
					if ($this->f($field)->get('localized'))
						$field .= LL;
					if (strpos($temp, '-') !== false)
						$o[] = qi($field) . " DESC";
					else
						$o[] = qi($field);
				}
				$query .= " ORDER BY " . implode (", ", $o);
			}

			if ($limit) // count, offset
				$query .= " LIMIT " . (is_array($limit) ? ($limit[1] . ", " . $limit[0]) : $limit);

			return $query;
		}

		public function sql($query, $arguments = array())
		{
			$select = (strtoupper(substr($query, 0, 6)) == 'SELECT');
			/*$query = preg_replace_callback('~\|([\w:\./\~]+?)\|~', array($this, 'sqlPrepareCallback'), $query);
			foreach ($arguments as $k => $v)
				$query = str_replace('|%' . $k . '%|', qe($v), $query);*/
			//TODO upravit syntaxi

			if ($objects = $this->queryCache($query))
			{
				lll('storage', $this->i . ':sql-cached', $query, Log::EVENT_STORAGE);
				return $objects;
			}

			lll('storage', $this->i . ':sql', $query, Log::EVENT_STORAGE);

			$result = ($select ? qa($query) : q($query));

			if ($select)
			{
				$objects = $this->fromArray($result);
				$this->queryCache($query, $objects);
				return $objects;
			}
			else
				return true;
		}

		public function load($filter = null, $order = null, $limit = null)
		{
			$query = $this->sqlSelect($filter, (($order === true) ? null : $order), (($order === true) ? 1 : $limit));
			$objects = $this->sql($query);
			if (($order === true) && ($objects instanceof Objects))
				return $objects->first();
			return $objects;
		}

		public function loadOne($value, $field = 'oid')
		{
			return $this->load(Feq($field, $value), true);
		}

		public function count($filter = null)
		{
			$query = $this->sqlSelect($filter, null, null, true);
			lll('storage', $this->i . ':count', $query, Log::EVENT_STORAGE);
			$result = qo($query);

			return (int) $result;
		}

		public function sqlArray($insert = false)
		{
			$data = array();
			foreach ($this->fields()->iterator() as $field)
				if ($field->isReal(Field::REAL_SAVE))
				{
					$k = $this->prefixField($field->id);
					$v = $field->get($this);
					/*if (($v == $field->default) && !$v) // zruseno kvuli vyhledavani, je treba ukladat i nulove hodnoty
						$v = null;*/
					$v = $field->convert($v, 'sql');
					$temp = $insert;
					if (!$temp)
					{
						$o = null;
						if (isset($this->o[$k]) && ($field->type != Field::TYPE_OBJECT))
							$o = $field->convert($this->o[$k], 'sql');
						if ($o != $v)
							$temp = true;
					}
					if ($temp)
						$data[$k] = $v;
				}
			return $data;
		}

		public function save($object = null)
		{
			if (is_array($object))
			{
				foreach ($object as $o)
					$this->save($o);
				return;
			}
			else if ($object !== null)
				return $object->save();

			$this->queryCache();
			//Event::invoke(array("object.save.before", "object.{$this->i}.save.before"), array('object' => $this));
			$proceed = $this->event('save.before');
			if ($proceed === false)
				return false;

			$this->ostatus = 'normal';
			$insert = false;
			if (!$this->oid)
			{
				$this->generate();
				$insert = true;
			}
			else if (!$this->count(Feq('oid', $this->oid)))
				$insert = true;

			$data = $this->sqlArray($insert);

			lll('storage', $this->i . ':save' . ($insert ? '-insert' : '-update'), $this->oid . ' - ' . count($data) . ': ' . implode(', ', array_keys($data)), Log::EVENT_STORAGE);
			
			if ($data)
			{
				if ($insert)
					$result = qinsertdirect($this->get('table'), $data, '');
				else
				{
					$data['oid'] = $this->oid;
					$result = qupdatedirect($this->get('table'), $data, 'oid', '');
				}
			}
			else
				$result = true;

			$this->event('save.after');
			//Event::invoke(array("object.save.after", "object.{$this->i}.save.after"), array('object' => $this));
			self::$_->{$this->oid} = $this;
		}

		public function delete($filter = null, $tree = false)
		{
			$this->queryCache();

			if (($filter !== null) || (!$this->oid))
			{
				$objects = $this->load($filter);
				foreach ($objects->iterator() as $object)
					$object->delete(null, $tree);
			}
			else
			{
				lll('storage', $this->i . ':delete', $this->oid, Log::EVENT_STORAGE);
				//Event::invoke(array("object.delete.before", "object.{$this->i}.delete.before"), array('object' => $this));
				$proceed = $this->event('delete.before');
				if ($proceed !== false)
				{
					if ($tree)
						foreach ($this->children(0)->iterator() as $child)
							$child->delete(null, true);
					q("DELETE FROM ".qi($this->get('table'))." WHERE ".qi('oid')."=".qq($this->oid));
					$this->ostatus = 'deleted';
					$this->event('delete.after');
					//Event::invoke(array("object.delete.after", "object.{$this->i}.delete.after"), array('object' => $this));
				}
			}
		}
	}

	class Objects implements Iterator
	{
		public					$_									= array();

		public function __get($id)
		{
			if (!$id)
			{
				$temp = array_values($this->_);
				return (count($temp) ? $temp[0] : false);
			}
			if (isset($this->_[$id]))
				return $this->_[$id];
			else
				return null;
		}

		public function __set($id, $value)
		{
			if (!$id)
			{
				$value->generate();
				$this->_[$value->oid] = $value;
			}
			else
				$this->_[$id] = $value;

			if ($this !== object::$_)
				object::$_->$id = $value;
		}

		public function __isset($id)
		{
			return isset($this->_[$id]);
		}

		public function __unset($id)
		{
			unset($this->_[$id]);
		}

		public function size()						{ return count($this->_); }
		public function count()						{ return count($this->_); }
		public function rewind()					{ reset($this->_); }
		public function current()					{ return current($this->_); }
		public function key()						{ return key($this->_); }
		public function keys()						{ return array_keys($this->_); }
		public function next()						{ return next($this->_); }
		public function valid()						{ return ($this->current() !== false); }

		public function iterator()
		{
			return new ContainerIterator($this);
		}
		
		public function reverse()
		{
			$result = new Objects();
			$result->_ = array_reverse($this->_);
			return $result;
		}

		public function __construct()
		{
			$args = func_get_args();
			foreach ($args as $arg)
				$this->add($arg);
		}

		public function first()
		{
			if (!count($this->_))
				return null;
			return array_shift(array_slice($this->_, 0, 1));
		}

		public function last()
		{
			if (!count($this->_))
				return null;
			return array_shift(array_values(array_slice($this->_, -1, 1)));
		}
		
		public function slice($offset, $length = null)
		{
			$result = new Objects();
			$result->_ = array_slice($this->_, $offset, $length);
			return $result;
		}

		public function add($arg, $key = null)
		{
			if ($arg instanceof Objects)
			{
				foreach ($arg->iterator() as $object)
					$this->add($object);
			}
			else
			{
				if ($key === null)
					$key = $arg->oid;
				if ($key)
					$this->_[$key] = $arg;
				else
					$this->_[] = $arg;
			}
		}
	}

	class Filter
	{
		public				$a								= null;
		public				$b								= null;

		public function __construct($a = null, $b = null)
		{
			$this->a = $a;
			$this->b = $b;
		}

		public function getQuotedField($object = null)
		{
			return qi($this->a);
		}

		public function all()
		{
			return array($this);
		}

		public function sql($options)
		{
			return "(1)";
		}

		public function validate($options)
		{
			return true;
		}

		public function prepare()
		{
			if (!is_object($this->a))
				$this->a = Ff($this->a);
			if (!is_object($this->b))
				$this->b = Fc($this->b);
		}

		function __clone()
		{
			foreach($this as $k => $v)
				if(gettype($v) == 'object')
					$this->$k = clone($this->$k);
				else if (gettype($v) == 'array')
				{
					$temp = array();
					foreach ($v as $kk => $vv)
						if (gettype($vv) == 'object')
							$temp[$kk] = clone($vv);
						else
							$temp[$kk] = $vv;
					$this->$k = $temp;
				}
		}
		
		/*function factoryConstant($_value, $options = array())
		{
			extract($options, EXTR_SKIP);
			
			if (!isset($object))
				return $_value;
			
			
				
						$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			
		}*/
	}

	class FilterField extends Filter
	{
		public $field = null;
		
		public function prepare() {}

		public function fid()
		{
			$temp = strpos($this->a, '.');
			return ($temp ? substr($this->a, $temp + 1) : $this->a);
		}

		public function oid()
		{
			$temp = strpos($this->a, '.');
			return ($temp ? substr($this->a, 0, $temp) : null);
		}

		public function sql($options)
		{
			extract($options, EXTR_SKIP);
			if (strpos($this->a, '.') !== false)
			{
				if ($this->b)
				{
					list($x, $y) = explode('.', $this->a);
					return qi($x) . '.' . qi($y);
				}
				else
				{
					if (!$field = @Field::$_[$this->a])
						return '[error field '.$this->a.']';
					return $field->sqlName($object);
				}
			}
			else
				return $object->f($this->a)->sqlName();
		}

		public function validate($options)
		{
			extract($options, EXTR_SKIP);
			return $object->{$this->a};
		}
	}

	class FilterConstant extends Filter
	{
		public $field = null;
		
		public function prepare() {}

		public function quote($value)
		{
			if ($this->field && ($this->field instanceof Field))
				return qq($this->field->$this->convert($value, 'sql'));
			return qq($value);
		}
		
		public static function quoteArray($array)
		{
			$result = array();
			foreach ($array as $k => $v)
				$result[$k] = qq($v);
			return implode(', ', $result);
		}
		
		public function sql($options)
		{
			extract($options, EXTR_SKIP);
			if (is_array($this->a))
			{
				$result = array();
				foreach ($this->a as $temp)
					$result[] = $this->quote($temp);
				return implode(', ', $result);
			}
			else
				return $this->quote($this->a);
		}

		public function validate($options)
		{
			extract($options, EXTR_SKIP);
			return $this->a;
		}
	}

	class FilterNot extends Filter
	{
		public function prepare()
		{
			$this->a->prepare();
		}

		public function all()
		{
			return array_merge(array($this), $this->a->all());
		}

		public function sql($options)
		{
			if (!$this->a)
				return null;
			return '(NOT ' . $this->a->sql($options) . ')';
		}

		public function validate($options)
		{
			return !$this->a->validate($options);
		}
	}

	class FilterAnd extends Filter
	{
		public function prepare()
		{
			if (!is_array($this->a))
				fbo($this->a);
			foreach ($this->a as $x)
				if ($x !== null)
					$x->prepare();
		}

		public function all()
		{
			$result = array($this);
			foreach ($this->a as $x)
				if ($x !== null)
					$result = array_merge($result, $x->all());
			return $result;
		}

		public function sql($options)
		{
			$result = array();
			if (!is_array($this->a))
				fbo($this->a);
			foreach ($this->a as $x)
				if (($x !== null) && ($temp = $x->sql($options)))
					$result[] = '(' . $temp . ')';
			return implode(' AND ', $result);
		}

		public function validate($options)
		{
			$result = true;
			foreach ($this->a as $x)
				if (($x !== null) && (($temp = $x->validate($options)) !== null))
					$result = $result && $temp;
			return $result;
		}
	}

	class FilterOr extends Filter
	{
		public function prepare()
		{
			if (!is_array($this->a))
				fbo($this->a);
			foreach ($this->a as $x)
				if ($x !== null)
					$x->prepare();
		}

		public function all()
		{
			$result = array($this);
			if (!is_array($this->a))
				fbo($this->a);
			foreach ($this->a as $x)
				if ($x !== null)
					$result = array_merge($result, $x->all());
			return $result;
		}

		public function sql($options)
		{
			$result = array();
			foreach ($this->a as $x)
				if (($x !== null) && ($temp = $x->sql($options)))
					$result[] = '(' . $temp . ')';
			return implode(' OR ', $result);
		}

		public function validate($options)
		{
			$result = false;
			foreach ($this->a as $x)
				if (($x !== null) && (($temp = $x->validate($options)) !== null))
					$result = $result || $temp;
			return $result;
		}
	}

	class FilterEquals extends Filter
	{
		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			return $a . (($b === null) ? ' IS NULL' : (' = ' . $b));
		}

		public function validate($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->validate($options) : Ff($this->a)->validate($options));
			$b = (($this->b instanceof Filter) ? $this->b->validate($options) : Fc($this->b)->validate($options));
			extract($options, EXTR_SKIP);

			return ($a == $b);
		}
	}

	class FilterFullText extends Filter
	{
		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			return 'MATCH (' . $a . ') AGAINST (' . $b . ' IN BOOLEAN MODE)';
		}
	}

	class FilterLike extends Filter
	{
		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			return $a . ' LIKE ' . $b;
		}

		public function validate($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->validate($options) : Ff($this->a)->validate($options));
			$b = (($this->b instanceof Filter) ? $this->b->validate($options) : Fc($this->b)->validate($options));
			extract($options, EXTR_SKIP);

			$pattern = str_replace(
				array("\\", "^", "\$", ".", "[", "]", "|", "(", ")", "?", "*", "+", "{", "}", "%", "_"),
				array("\\\\", "\\^", "\\\$", "\\.", "\\[", "\\]", "\\|", "\\(", "\\)", "\\?", "\\*", "\\+", "\\{", "\\}", ".*", "."),
				$b);
			return (boolean) preg_match('~' . $pattern . '~imu', $a);
		}
	}

	class FilterRegEx extends Filter
	{
		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			return $a . ' REGEXP ' . $b;
		}

		public function validate($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->validate($options) : Ff($this->a)->validate($options));
			$b = (($this->b instanceof Filter) ? $this->b->validate($options) : Fc($this->b)->validate($options));
			extract($options, EXTR_SKIP);

			return (boolean) preg_match('~' . $b . '~imu', $a);
		}
	}

	class FilterIn extends Filter
	{
		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			if (!$b)
				return 'FALSE';
			else
				return $a . ' IN (' . $b . ')';
		}

		public function validate($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->validate($options) : Ff($this->a)->validate($options));
			$b = (($this->b instanceof Filter) ? $this->b->validate($options) : Fc($this->b)->validate($options));
			extract($options, EXTR_SKIP);

			foreach ($b as $temp)
				if ($a == $temp)
					return true;
			return false;
		}
	}

	class FilterOperator extends Filter
	{
		public					$operator			= null;

		public function __construct($a, $b, $operator)
		{
			parent::__construct($a, $b);
			$this->operator = $operator;
		}

		public function sql($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = (($this->b instanceof Filter) ? $this->b->sql($options) : Fc($this->b)->sql($options));
			extract($options, EXTR_SKIP);

			$op = '[error]';
			switch ($this->operator)
			{
				case '==':
					$op = '=';
					break;
				case '!=':
				case '>':
				case '>=':
				case '<':
				case '<=':
					$op = $this->operator;
					break;
			}

			return $a . ' ' . $op . ' ' . $b;
		}

		public function validate($options)
		{
			$a = (($this->a instanceof Filter) ? $this->a->validate($options) : Ff($this->a)->validate($options));
			$b = (($this->b instanceof Filter) ? $this->b->validate($options) : Fc($this->b)->validate($options));
			extract($options, EXTR_SKIP);

			switch ($this->operator)
			{
				case '==':
					$result = ($a == $b);
					break;
				case '!=':
					$result = ($a != $b);
					break;
				case '>':
					$result = ($a > $b);
					break;
				case '>=':
					$result = ($a >= $b);
					break;
				case '<':
					$result = ($a < $b);
					break;
				case '<=':
					$result = ($a <= $b);
					break;
			}
			return $result;
		}
	}

	class FilterSql extends Filter
	{
		public function sql($options)
		{
			$a = null;
			if ($this->a)
				$a = (($this->a instanceof Filter) ? $this->a->sql($options) : Ff($this->a)->sql($options));
			$b = $this->b;
			//extract($options, EXTR_SKIP);
			return "(" . str_replace("~lval~", $a, $b) . ")";
		}
	}

	class Ff	extends FilterField		{}
	class Fc	extends FilterConstant	{}
	class Fand	extends FilterAnd		{}
	class Fo	extends FilterOr		{}
	class Fnot	extends FilterNot		{}
	class Feq	extends FilterEquals	{}
	class Flike	extends FilterLike		{}
	class Fft	extends FilterFullText	{}
	class Fre	extends FilterRegEx		{}
	class Fin	extends FilterIn		{}
	class Fop	extends FilterOperator	{}
	class Fsql	extends FilterSql		{}

	function Ff($a)				{ return new Ff($a); }
	function Fc($a)				{ return new Fc($a); }
	function Fand()				{ return new Fand(func_get_args()); }
	function Fo()				{ return new Fo(func_get_args()); }
	function Fnot($a)			{ return new Fnot($a); }
	function Feq($a, $b)		{ return new Feq($a, $b); }
	function Flike($a, $b)		{ return new Flike($a, $b); }
	function Fft($a, $b)		{ return new Fft($a, $b); }
	function Fre($a, $b)		{ return new Fre($a, $b); }
	function Fin($a, $b)		{ return new Fin($a, $b); }
	function Fop($a, $b, $op)	{ return new Fop($a, $b, $op); }
	function Fsql($a, $b)		{ return new Fsql($a, $b); }
	
	function o($id) 
	{
		if (isset($GLOBALS['objects'][$id]))
			return $GLOBALS['objects'][$id];
		else
			return null;
	}
	
	object::$_ = new Objects();
?>

ACC SHELL 2018